├── .gitignore ├── README.md ├── nest-cli.json ├── package-lock.json ├── package.json ├── src ├── app.module.ts ├── authz │ ├── authz.module.ts │ └── jwt.strategy.ts ├── item.ts ├── items.ts ├── items │ ├── items.controller.ts │ ├── items.module.ts │ └── items.service.ts ├── main.ts ├── permissions.decorator.ts └── permissions.guard.ts ├── tsconfig.build.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows,intellij,webstorm,jetbrains,visualstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=node,linux,macos,windows,intellij,webstorm,jetbrains,visualstudio,visualstudiocode 4 | 5 | ### Intellij ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff 10 | .idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/**/usage.statistics.xml 13 | .idea/**/dictionaries 14 | .idea/**/shelf 15 | 16 | # Generated files 17 | .idea/**/contentModel.xml 18 | 19 | # Sensitive or high-churn files 20 | .idea/**/dataSources/ 21 | .idea/**/dataSources.ids 22 | .idea/**/dataSources.local.xml 23 | .idea/**/sqlDataSources.xml 24 | .idea/**/dynamic.xml 25 | .idea/**/uiDesigner.xml 26 | .idea/**/dbnavigator.xml 27 | 28 | # Gradle 29 | .idea/**/gradle.xml 30 | .idea/**/libraries 31 | 32 | # Gradle and Maven with auto-import 33 | # When using Gradle or Maven with auto-import, you should exclude module files, 34 | # since they will be recreated, and may cause churn. Uncomment if using 35 | # auto-import. 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | 40 | # CMake 41 | cmake-build-*/ 42 | 43 | # Mongo Explorer plugin 44 | .idea/**/mongoSettings.xml 45 | 46 | # File-based project format 47 | *.iws 48 | 49 | # IntelliJ 50 | out/ 51 | 52 | # mpeltonen/sbt-idea plugin 53 | .idea_modules/ 54 | 55 | # JIRA plugin 56 | atlassian-ide-plugin.xml 57 | 58 | # Cursive Clojure plugin 59 | .idea/replstate.xml 60 | 61 | # Crashlytics plugin (for Android Studio and IntelliJ) 62 | com_crashlytics_export_strings.xml 63 | crashlytics.properties 64 | crashlytics-build.properties 65 | fabric.properties 66 | 67 | # Editor-based Rest Client 68 | .idea/httpRequests 69 | 70 | # Android studio 3.1+ serialized cache file 71 | .idea/caches/build_file_checksums.ser 72 | 73 | # JetBrains templates 74 | **___jb_tmp___ 75 | 76 | ### Intellij Patch ### 77 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 78 | 79 | # *.iml 80 | # modules.xml 81 | # .idea/misc.xml 82 | # *.ipr 83 | 84 | # Sonarlint plugin 85 | .idea/sonarlint 86 | 87 | ### JetBrains ### 88 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 89 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 90 | 91 | # User-specific stuff 92 | 93 | # Generated files 94 | 95 | # Sensitive or high-churn files 96 | 97 | # Gradle 98 | 99 | # Gradle and Maven with auto-import 100 | # When using Gradle or Maven with auto-import, you should exclude module files, 101 | # since they will be recreated, and may cause churn. Uncomment if using 102 | # auto-import. 103 | # .idea/modules.xml 104 | # .idea/*.iml 105 | # .idea/modules 106 | 107 | # CMake 108 | 109 | # Mongo Explorer plugin 110 | 111 | # File-based project format 112 | 113 | # IntelliJ 114 | 115 | # mpeltonen/sbt-idea plugin 116 | 117 | # JIRA plugin 118 | 119 | # Cursive Clojure plugin 120 | 121 | # Crashlytics plugin (for Android Studio and IntelliJ) 122 | 123 | # Editor-based Rest Client 124 | 125 | # Android studio 3.1+ serialized cache file 126 | 127 | # JetBrains templates 128 | 129 | ### JetBrains Patch ### 130 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 131 | 132 | # *.iml 133 | # modules.xml 134 | # .idea/misc.xml 135 | # *.ipr 136 | 137 | # Sonarlint plugin 138 | 139 | ### Linux ### 140 | *~ 141 | 142 | # temporary files which can be created if a process still has a handle open of a deleted file 143 | .fuse_hidden* 144 | 145 | # KDE directory preferences 146 | .directory 147 | 148 | # Linux trash folder which might appear on any partition or disk 149 | .Trash-* 150 | 151 | # .nfs files are created when an open file is removed but is still being accessed 152 | .nfs* 153 | 154 | ### macOS ### 155 | # General 156 | .DS_Store 157 | .AppleDouble 158 | .LSOverride 159 | 160 | # Icon must end with two \r 161 | Icon 162 | 163 | # Thumbnails 164 | ._* 165 | 166 | # Files that might appear in the root of a volume 167 | .DocumentRevisions-V100 168 | .fseventsd 169 | .Spotlight-V100 170 | .TemporaryItems 171 | .Trashes 172 | .VolumeIcon.icns 173 | .com.apple.timemachine.donotpresent 174 | 175 | # Directories potentially created on remote AFP share 176 | .AppleDB 177 | .AppleDesktop 178 | Network Trash Folder 179 | Temporary Items 180 | .apdisk 181 | 182 | ### Node ### 183 | # Logs 184 | logs 185 | *.log 186 | npm-debug.log* 187 | yarn-debug.log* 188 | yarn-error.log* 189 | lerna-debug.log* 190 | 191 | # Diagnostic reports (https://nodejs.org/api/report.html) 192 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 193 | 194 | # Runtime data 195 | pids 196 | *.pid 197 | *.seed 198 | *.pid.lock 199 | 200 | # Directory for instrumented libs generated by jscoverage/JSCover 201 | lib-cov 202 | 203 | # Coverage directory used by tools like istanbul 204 | coverage 205 | 206 | # nyc test coverage 207 | .nyc_output 208 | 209 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 210 | .grunt 211 | 212 | # Bower dependency directory (https://bower.io/) 213 | bower_components 214 | 215 | # node-waf configuration 216 | .lock-wscript 217 | 218 | # Compiled binary addons (https://nodejs.org/api/addons.html) 219 | build/Release 220 | 221 | # Dependency directories 222 | node_modules/ 223 | jspm_packages/ 224 | 225 | # TypeScript v1 declaration files 226 | typings/ 227 | 228 | # Optional npm cache directory 229 | .npm 230 | 231 | # Optional eslint cache 232 | .eslintcache 233 | 234 | # Optional REPL history 235 | .node_repl_history 236 | 237 | # Output of 'npm pack' 238 | *.tgz 239 | 240 | # Yarn Integrity file 241 | .yarn-integrity 242 | 243 | # dotenv environment variables file 244 | .env 245 | .env.test 246 | 247 | # parcel-bundler cache (https://parceljs.org/) 248 | .cache 249 | 250 | # next.js build output 251 | .next 252 | 253 | # nuxt.js build output 254 | .nuxt 255 | 256 | # vuepress build output 257 | .vuepress/dist 258 | 259 | # Serverless directories 260 | .serverless/ 261 | 262 | # FuseBox cache 263 | .fusebox/ 264 | 265 | # DynamoDB Local files 266 | .dynamodb/ 267 | 268 | ### VisualStudioCode ### 269 | .vscode/* 270 | !.vscode/settings.json 271 | !.vscode/tasks.json 272 | !.vscode/launch.json 273 | !.vscode/extensions.json 274 | 275 | ### VisualStudioCode Patch ### 276 | # Ignore all local history of files 277 | .history 278 | 279 | ### WebStorm ### 280 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 281 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 282 | 283 | # User-specific stuff 284 | 285 | # Generated files 286 | 287 | # Sensitive or high-churn files 288 | 289 | # Gradle 290 | 291 | # Gradle and Maven with auto-import 292 | # When using Gradle or Maven with auto-import, you should exclude module files, 293 | # since they will be recreated, and may cause churn. Uncomment if using 294 | # auto-import. 295 | # .idea/modules.xml 296 | # .idea/*.iml 297 | # .idea/modules 298 | 299 | # CMake 300 | 301 | # Mongo Explorer plugin 302 | 303 | # File-based project format 304 | 305 | # IntelliJ 306 | 307 | # mpeltonen/sbt-idea plugin 308 | 309 | # JIRA plugin 310 | 311 | # Cursive Clojure plugin 312 | 313 | # Crashlytics plugin (for Android Studio and IntelliJ) 314 | 315 | # Editor-based Rest Client 316 | 317 | # Android studio 3.1+ serialized cache file 318 | 319 | # JetBrains templates 320 | 321 | ### WebStorm Patch ### 322 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 323 | 324 | # *.iml 325 | # modules.xml 326 | # .idea/misc.xml 327 | # *.ipr 328 | 329 | # Sonarlint plugin 330 | 331 | ### Windows ### 332 | # Windows thumbnail cache files 333 | Thumbs.db 334 | ehthumbs.db 335 | ehthumbs_vista.db 336 | 337 | # Dump file 338 | *.stackdump 339 | 340 | # Folder config file 341 | [Dd]esktop.ini 342 | 343 | # Recycle Bin used on file shares 344 | $RECYCLE.BIN/ 345 | 346 | # Windows Installer files 347 | *.cab 348 | *.msi 349 | *.msix 350 | *.msm 351 | *.msp 352 | 353 | # Windows shortcuts 354 | *.lnk 355 | 356 | ### VisualStudio ### 357 | ## Ignore Visual Studio temporary files, build results, and 358 | ## files generated by popular Visual Studio add-ons. 359 | ## 360 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 361 | 362 | # User-specific files 363 | *.rsuser 364 | *.suo 365 | *.user 366 | *.userosscache 367 | *.sln.docstates 368 | 369 | # User-specific files (MonoDevelop/Xamarin Studio) 370 | *.userprefs 371 | 372 | # Mono auto generated files 373 | mono_crash.* 374 | 375 | # Build results 376 | [Dd]ebug/ 377 | [Dd]ebugPublic/ 378 | [Rr]elease/ 379 | [Rr]eleases/ 380 | x64/ 381 | x86/ 382 | [Aa][Rr][Mm]/ 383 | [Aa][Rr][Mm]64/ 384 | bld/ 385 | [Bb]in/ 386 | [Oo]bj/ 387 | [Ll]og/ 388 | 389 | # Visual Studio 2015/2017 cache/options directory 390 | .vs/ 391 | # Uncomment if you have tasks that create the project's static files in wwwroot 392 | #wwwroot/ 393 | 394 | # Visual Studio 2017 auto generated files 395 | Generated\ Files/ 396 | 397 | # MSTest test Results 398 | [Tt]est[Rr]esult*/ 399 | [Bb]uild[Ll]og.* 400 | 401 | # NUNIT 402 | *.VisualState.xml 403 | TestResult.xml 404 | 405 | # Build Results of an ATL Project 406 | [Dd]ebugPS/ 407 | [Rr]eleasePS/ 408 | dlldata.c 409 | 410 | # Benchmark Results 411 | BenchmarkDotNet.Artifacts/ 412 | 413 | # .NET Core 414 | project.lock.json 415 | project.fragment.lock.json 416 | artifacts/ 417 | 418 | # StyleCop 419 | StyleCopReport.xml 420 | 421 | # Files built by Visual Studio 422 | *_i.c 423 | *_p.c 424 | *_h.h 425 | *.ilk 426 | *.meta 427 | *.obj 428 | *.iobj 429 | *.pch 430 | *.pdb 431 | *.ipdb 432 | *.pgc 433 | *.pgd 434 | *.rsp 435 | *.sbr 436 | *.tlb 437 | *.tli 438 | *.tlh 439 | *.tmp 440 | *.tmp_proj 441 | *_wpftmp.csproj 442 | *.vspscc 443 | *.vssscc 444 | .builds 445 | *.pidb 446 | *.svclog 447 | *.scc 448 | 449 | # Chutzpah Test files 450 | _Chutzpah* 451 | 452 | # Visual C++ cache files 453 | ipch/ 454 | *.aps 455 | *.ncb 456 | *.opendb 457 | *.opensdf 458 | *.sdf 459 | *.cachefile 460 | *.VC.db 461 | *.VC.VC.opendb 462 | 463 | # Visual Studio profiler 464 | *.psess 465 | *.vsp 466 | *.vspx 467 | *.sap 468 | 469 | # Visual Studio Trace Files 470 | *.e2e 471 | 472 | # TFS 2012 Local Workspace 473 | $tf/ 474 | 475 | # Guidance Automation Toolkit 476 | *.gpState 477 | 478 | # ReSharper is a .NET coding add-in 479 | _ReSharper*/ 480 | *.[Rr]e[Ss]harper 481 | *.DotSettings.user 482 | 483 | # JustCode is a .NET coding add-in 484 | .JustCode 485 | 486 | # TeamCity is a build add-in 487 | _TeamCity* 488 | 489 | # DotCover is a Code Coverage Tool 490 | *.dotCover 491 | 492 | # AxoCover is a Code Coverage Tool 493 | .axoCover/* 494 | !.axoCover/settings.json 495 | 496 | # Visual Studio code coverage results 497 | *.coverage 498 | *.coveragexml 499 | 500 | # NCrunch 501 | _NCrunch_* 502 | .*crunch*.local.xml 503 | nCrunchTemp_* 504 | 505 | # MightyMoose 506 | *.mm.* 507 | AutoTest.Net/ 508 | 509 | # Web workbench (sass) 510 | .sass-cache/ 511 | 512 | # Installshield output folder 513 | [Ee]xpress/ 514 | 515 | # DocProject is a documentation generator add-in 516 | DocProject/buildhelp/ 517 | DocProject/Help/*.HxT 518 | DocProject/Help/*.HxC 519 | DocProject/Help/*.hhc 520 | DocProject/Help/*.hhk 521 | DocProject/Help/*.hhp 522 | DocProject/Help/Html2 523 | DocProject/Help/html 524 | 525 | # Click-Once directory 526 | publish/ 527 | 528 | # Publish Web Output 529 | *.[Pp]ublish.xml 530 | *.azurePubxml 531 | # Note: Comment the next line if you want to checkin your web deploy settings, 532 | # but database connection strings (with potential passwords) will be unencrypted 533 | *.pubxml 534 | *.publishproj 535 | 536 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 537 | # checkin your Azure Web App publish settings, but sensitive information contained 538 | # in these scripts will be unencrypted 539 | PublishScripts/ 540 | 541 | # NuGet Packages 542 | *.nupkg 543 | # The packages folder can be ignored because of Package Restore 544 | **/[Pp]ackages/* 545 | # except build/, which is used as an MSBuild target. 546 | !**/[Pp]ackages/build/ 547 | # Uncomment if necessary however generally it will be regenerated when needed 548 | #!**/[Pp]ackages/repositories.config 549 | # NuGet v3's project.json files produces more ignorable files 550 | *.nuget.props 551 | *.nuget.targets 552 | 553 | # Microsoft Azure Build Output 554 | csx/ 555 | *.build.csdef 556 | 557 | # Microsoft Azure Emulator 558 | ecf/ 559 | rcf/ 560 | 561 | # Windows Store app package directories and files 562 | AppPackages/ 563 | BundleArtifacts/ 564 | Package.StoreAssociation.xml 565 | _pkginfo.txt 566 | *.appx 567 | *.appxbundle 568 | *.appxupload 569 | 570 | # Visual Studio cache files 571 | # files ending in .cache can be ignored 572 | *.[Cc]ache 573 | # but keep track of directories ending in .cache 574 | !?*.[Cc]ache/ 575 | 576 | # Others 577 | ClientBin/ 578 | ~$* 579 | *.dbmdl 580 | *.dbproj.schemaview 581 | *.jfm 582 | *.pfx 583 | *.publishsettings 584 | orleans.codegen.cs 585 | 586 | # Including strong name files can present a security risk 587 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 588 | #*.snk 589 | 590 | # Since there are multiple workflows, uncomment next line to ignore bower_components 591 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 592 | #bower_components/ 593 | 594 | # RIA/Silverlight projects 595 | Generated_Code/ 596 | 597 | # Backup & report files from converting an old project file 598 | # to a newer Visual Studio version. Backup files are not needed, 599 | # because we have git ;-) 600 | _UpgradeReport_Files/ 601 | Backup*/ 602 | UpgradeLog*.XML 603 | UpgradeLog*.htm 604 | ServiceFabricBackup/ 605 | *.rptproj.bak 606 | 607 | # SQL Server files 608 | *.mdf 609 | *.ldf 610 | *.ndf 611 | 612 | # Business Intelligence projects 613 | *.rdl.data 614 | *.bim.layout 615 | *.bim_*.settings 616 | *.rptproj.rsuser 617 | *- Backup*.rdl 618 | 619 | # Microsoft Fakes 620 | FakesAssemblies/ 621 | 622 | # GhostDoc plugin setting file 623 | *.GhostDoc.xml 624 | 625 | # Node.js Tools for Visual Studio 626 | .ntvs_analysis.dat 627 | 628 | # Visual Studio 6 build log 629 | *.plg 630 | 631 | # Visual Studio 6 workspace options file 632 | *.opt 633 | 634 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 635 | *.vbw 636 | 637 | # Visual Studio LightSwitch build output 638 | **/*.HTMLClient/GeneratedArtifacts 639 | **/*.DesktopClient/GeneratedArtifacts 640 | **/*.DesktopClient/ModelManifest.xml 641 | **/*.Server/GeneratedArtifacts 642 | **/*.Server/ModelManifest.xml 643 | _Pvt_Extensions 644 | 645 | # Paket dependency manager 646 | .paket/paket.exe 647 | paket-files/ 648 | 649 | # FAKE - F# Make 650 | .fake/ 651 | 652 | # CodeRush personal settings 653 | .cr/personal 654 | 655 | # Python Tools for Visual Studio (PTVS) 656 | __pycache__/ 657 | *.pyc 658 | 659 | # Cake - Uncomment if you are using it 660 | # tools/** 661 | # !tools/packages.config 662 | 663 | # Tabs Studio 664 | *.tss 665 | 666 | # Telerik's JustMock configuration file 667 | *.jmconfig 668 | 669 | # BizTalk build output 670 | *.btp.cs 671 | *.btm.cs 672 | *.odx.cs 673 | *.xsd.cs 674 | 675 | # OpenCover UI analysis results 676 | OpenCover/ 677 | 678 | # Azure Stream Analytics local run output 679 | ASALocalRun/ 680 | 681 | # MSBuild Binary and Structured Log 682 | *.binlog 683 | 684 | # NVidia Nsight GPU debugger configuration file 685 | *.nvuser 686 | 687 | # MFractors (Xamarin productivity tool) working folder 688 | .mfractor/ 689 | 690 | # Local History for Visual Studio 691 | .localhistory/ 692 | 693 | # BeatPulse healthcheck temp database 694 | healthchecksdb 695 | 696 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 697 | MigrationBackup/ 698 | 699 | # End of https://www.gitignore.io/api/node,linux,macos,windows,intellij,webstorm,jetbrains,visualstudio,visualstudiocode 700 | 701 | .idea/ 702 | 703 | dist/ 704 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developing a Secure API with NestJS 2 | 3 | [Click here to read the full tutorial](https://auth0.com/blog/developing-a-secure-api-with-nestjs-adding-authorization/) 4 | 5 | Learn how to **build a feature-complete API using NestJS** that lets clients perform data operations on resources that describe a restaurant menu. Using TypeScript with Node.js gives you access to optional static type-checking along with robust tooling for large apps and the latest ECMAScript features. 6 | 7 | Learn also how to **define data models, create a data service, and quickly build modular endpoints**. As an option, you can also learn how to **secure the API using Auth0**. To see your API in action, you’ll use a production client called "WHATABYTE Dashboard", which is inspired by the [sleek web player from Spotify](https://open.spotify.com/search). 8 | 9 | ![WHATBYTE Dashboard menu item](https://cdn.auth0.com/blog/developing-a-secure-api-with-nestjs/whatabyte-dashboard-menu-item.png) 10 | 11 | ## Setup 12 | 13 | - Clone repository: 14 | 15 | ```bash 16 | git clone git@github.com:auth0-blog/wab-menu-api-nestjs.git \ 17 | nest-restaurant-api \ 18 | ``` 19 | 20 | - Make the project folder your current directory: 21 | 22 | ```bash 23 | cd nest-restaurant-api 24 | ``` 25 | 26 | - Install the project dependencies: 27 | 28 | ```bash 29 | npm i 30 | ``` 31 | 32 | - If you haven't set up any Auth0 applications, follow the steps from these tutorial sections to create them: 33 | 34 | Set Up API Authorization 35 | 36 | Register a Client Application with Auth0 37 | 38 | Connect a Client Application With Auth0 39 | 40 | - Create a `.env` hidden file: 41 | 42 | ```bash 43 | touch .env 44 | ``` 45 | 46 | - Populate `.env` with this: 47 | 48 | ```bash 49 | PORT=7000 50 | AUTH0_DOMAIN="Your Auth0 domain" 51 | AUTH0_AUDIENCE="Your Auth0 audience" 52 | ``` 53 | 54 | - Start the Express server: 55 | 56 | ```bash 57 | npm run start:dev 58 | ``` 59 | 60 | [Read on the complete tutorial](https://auth0.com/blog/developing-a-secure-api-with-nestjs-adding-authorization/) 61 | 62 | **If you have any questions or feedback, please [contact us through our Community Site for this tutorial](https://community.auth0.com/t/developing-a-secure-api-with-nestjs/33026).** 63 | 64 | ## About Auth0 65 | 66 | Auth0, the identity platform for application builders, provides thousands of enterprise customers with a Universal Identity Platform for their web, mobile, IoT, and internal applications. Its extensible platform seamlessly authenticates and secures more than 2.5B logins per month, making it loved by developers and trusted by global enterprises. The company's U.S. headquarters in Bellevue, WA, and additional offices in Buenos Aires, London, Tokyo, Sydney, and Singapore, support its customers that are located in 70+ countries. 67 | 68 | For more information, visit [https://auth0.com](https://auth0.com/) or follow [@auth0 on Twitter](https://twitter.com/auth0). 69 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-restaurant-api", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "license": "MIT", 7 | "scripts": { 8 | "prebuild": "rimraf dist", 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "nest start", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "tslint -p tsconfig.json -c tslint.json", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^6.7.2", 24 | "@nestjs/core": "^6.7.2", 25 | "@nestjs/passport": "^6.1.1", 26 | "@nestjs/platform-express": "^6.7.2", 27 | "class-transformer": "^0.2.3", 28 | "class-validator": "^0.11.0", 29 | "dotenv": "^8.2.0", 30 | "jwks-rsa": "^1.6.0", 31 | "passport": "^0.4.1", 32 | "passport-jwt": "^4.0.0", 33 | "reflect-metadata": "^0.1.13", 34 | "rimraf": "^3.0.0", 35 | "rxjs": "^6.5.3" 36 | }, 37 | "devDependencies": { 38 | "@nestjs/cli": "^6.13.2", 39 | "@nestjs/schematics": "^6.7.0", 40 | "@nestjs/testing": "^6.7.1", 41 | "@types/express": "^4.17.1", 42 | "@types/jest": "^24.0.18", 43 | "@types/node": "^12.7.5", 44 | "@types/supertest": "^2.0.8", 45 | "jest": "^24.9.0", 46 | "prettier": "^1.18.2", 47 | "supertest": "^4.0.2", 48 | "ts-jest": "^24.1.0", 49 | "ts-loader": "^6.1.1", 50 | "ts-node": "^8.4.1", 51 | "tsconfig-paths": "^3.9.0", 52 | "tslint": "^5.20.0", 53 | "typescript": "^3.6.3" 54 | }, 55 | "jest": { 56 | "moduleFileExtensions": [ 57 | "js", 58 | "json", 59 | "ts" 60 | ], 61 | "rootDir": "src", 62 | "testRegex": ".spec.ts$", 63 | "transform": { 64 | "^.+\\.(t|j)s$": "ts-jest" 65 | }, 66 | "coverageDirectory": "../coverage", 67 | "testEnvironment": "node" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | // src/app.module.ts 2 | 3 | import { Module } from '@nestjs/common'; 4 | import { ItemsModule } from './items/items.module'; 5 | import { AuthzModule } from './authz/authz.module'; 6 | 7 | @Module({ 8 | imports: [ItemsModule, AuthzModule], 9 | controllers: [], 10 | providers: [], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /src/authz/authz.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { JwtStrategy } from './jwt.strategy'; 4 | 5 | @Module({ 6 | imports: [PassportModule.register({ defaultStrategy: 'jwt' })], 7 | providers: [JwtStrategy], 8 | exports: [PassportModule], 9 | }) 10 | export class AuthzModule {} 11 | -------------------------------------------------------------------------------- /src/authz/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | // src/authz/jwt.strategy.ts 2 | 3 | import { Injectable } from '@nestjs/common'; 4 | import { PassportStrategy } from '@nestjs/passport'; 5 | import { ExtractJwt, Strategy } from 'passport-jwt'; 6 | import { passportJwtSecret } from 'jwks-rsa'; 7 | import * as dotenv from 'dotenv'; 8 | 9 | dotenv.config(); 10 | 11 | @Injectable() 12 | export class JwtStrategy extends PassportStrategy(Strategy) { 13 | constructor() { 14 | super({ 15 | secretOrKeyProvider: passportJwtSecret({ 16 | cache: true, 17 | rateLimit: true, 18 | jwksRequestsPerMinute: 5, 19 | jwksUri: `${process.env.AUTH0_DOMAIN}.well-known/jwks.json`, 20 | }), 21 | 22 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 23 | audience: process.env.AUTH0_AUDIENCE, 24 | issuer: `${process.env.AUTH0_DOMAIN}`, 25 | algorithms: ['RS256'], 26 | }); 27 | } 28 | 29 | validate(payload: any) { 30 | return payload; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/item.ts: -------------------------------------------------------------------------------- 1 | // src/item.ts 2 | 3 | import { IsString, IsNumber, IsOptional } from 'class-validator'; 4 | 5 | export class Item { 6 | @IsNumber() @IsOptional() readonly id: number; 7 | @IsString() readonly name: string; 8 | @IsNumber() readonly price: number; 9 | @IsString() readonly description: string; 10 | @IsString() readonly image: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/items.ts: -------------------------------------------------------------------------------- 1 | // src/items.ts 2 | 3 | import { Item } from './item'; 4 | 5 | export class Items { 6 | [key: number]: Item; 7 | } 8 | -------------------------------------------------------------------------------- /src/items/items.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Body, 3 | Controller, 4 | Delete, 5 | Get, 6 | Param, 7 | Post, 8 | Put, 9 | UseGuards, 10 | } from '@nestjs/common'; 11 | import { ItemsService } from './items.service'; 12 | import { Items } from '../items'; 13 | import { Item } from '../item'; 14 | import { AuthGuard } from '@nestjs/passport'; 15 | import { Permissions } from '../permissions.decorator'; 16 | import { PermissionsGuard } from '../permissions.guard'; 17 | 18 | @Controller('items') 19 | export class ItemsController { 20 | constructor(private readonly itemsService: ItemsService) {} 21 | 22 | @Get() 23 | async findAll(): Promise { 24 | return this.itemsService.findAll(); 25 | } 26 | 27 | @Get(':id') 28 | async find(@Param('id') id: number): Promise { 29 | return this.itemsService.find(id); 30 | } 31 | 32 | @UseGuards(AuthGuard('jwt'), PermissionsGuard) 33 | @Post() 34 | @Permissions('create:items') 35 | create(@Body('item') item: Item) { 36 | this.itemsService.create(item); 37 | } 38 | 39 | @UseGuards(AuthGuard('jwt'), PermissionsGuard) 40 | @Put() 41 | @Permissions('update:items') 42 | update(@Body('item') item: Item) { 43 | this.itemsService.update(item); 44 | } 45 | 46 | @UseGuards(AuthGuard('jwt'), PermissionsGuard) 47 | @Delete(':id') 48 | @Permissions('delete:items') 49 | delete(@Param('id') id: number) { 50 | this.itemsService.delete(id); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/items/items.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ItemsService } from './items.service'; 3 | import { ItemsController } from './items.controller'; 4 | 5 | @Module({ 6 | providers: [ItemsService], 7 | controllers: [ItemsController], 8 | }) 9 | export class ItemsModule {} 10 | -------------------------------------------------------------------------------- /src/items/items.service.ts: -------------------------------------------------------------------------------- 1 | // src/items/items.service.ts 2 | 3 | import { Injectable } from '@nestjs/common'; 4 | import { Item } from '../item'; 5 | import { Items } from '../items'; 6 | 7 | @Injectable() 8 | export class ItemsService { 9 | private readonly items: Items = { 10 | 1: { 11 | id: 1, 12 | name: 'Burger', 13 | price: 5.99, 14 | description: 'Tasty', 15 | image: 'https://cdn.auth0.com/blog/whatabyte/burger-sm.png', 16 | }, 17 | 2: { 18 | id: 2, 19 | name: 'Pizza', 20 | price: 2.99, 21 | description: 'Cheesy', 22 | image: 'https://cdn.auth0.com/blog/whatabyte/pizza-sm.png', 23 | }, 24 | 3: { 25 | id: 3, 26 | name: 'Tea', 27 | price: 1.99, 28 | description: 'Informative', 29 | image: 'https://cdn.auth0.com/blog/whatabyte/tea-sm.png', 30 | }, 31 | }; 32 | 33 | findAll(): Items { 34 | return this.items; 35 | } 36 | 37 | create(newItem: Item) { 38 | const id = new Date().valueOf(); 39 | this.items[id] = { 40 | ...newItem, 41 | id, 42 | }; 43 | } 44 | 45 | find(id: number): Item { 46 | const record: Item = this.items[id]; 47 | 48 | if (record) { 49 | return record; 50 | } 51 | 52 | throw new Error('No record found'); 53 | } 54 | 55 | update(updatedItem: Item) { 56 | if (this.items[updatedItem.id]) { 57 | this.items[updatedItem.id] = updatedItem; 58 | return; 59 | } 60 | 61 | throw new Error('No record found to update'); 62 | } 63 | 64 | delete(id: number) { 65 | const record: Item = this.items[id]; 66 | 67 | if (record) { 68 | delete this.items[id]; 69 | return; 70 | } 71 | 72 | throw new Error('No record found to delete'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // src/main.ts 2 | 3 | import { NestFactory } from '@nestjs/core'; 4 | import { AppModule } from './app.module'; 5 | import * as dotenv from 'dotenv'; 6 | import { ValidationPipe } from '@nestjs/common'; 7 | 8 | dotenv.config(); 9 | 10 | async function bootstrap() { 11 | const app = await NestFactory.create(AppModule); 12 | 13 | app.enableCors(); 14 | app.useGlobalPipes( 15 | new ValidationPipe({ 16 | disableErrorMessages: true, 17 | }), 18 | ); 19 | 20 | await app.listen(process.env.PORT); 21 | } 22 | bootstrap(); 23 | -------------------------------------------------------------------------------- /src/permissions.decorator.ts: -------------------------------------------------------------------------------- 1 | // src/permissions.decorator.ts 2 | 3 | import { SetMetadata } from '@nestjs/common'; 4 | 5 | export const Permissions = (...permissions: string[]) => 6 | SetMetadata('permissions', permissions); 7 | -------------------------------------------------------------------------------- /src/permissions.guard.ts: -------------------------------------------------------------------------------- 1 | // src/permissions.guard.ts 2 | 3 | import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; 4 | import { Observable } from 'rxjs'; 5 | import { Reflector } from '@nestjs/core'; 6 | 7 | @Injectable() 8 | export class PermissionsGuard implements CanActivate { 9 | constructor(private readonly reflector: Reflector) {} 10 | 11 | canActivate( 12 | context: ExecutionContext, 13 | ): boolean | Promise | Observable { 14 | const routePermissions = this.reflector.get( 15 | 'permissions', 16 | context.getHandler(), 17 | ); 18 | 19 | const userPermissions = context.getArgs()[0].user.permissions; 20 | 21 | if (!routePermissions) { 22 | return true; 23 | } 24 | 25 | const hasPermission = () => 26 | routePermissions.every(routePermission => 27 | userPermissions.includes(routePermission), 28 | ); 29 | 30 | return hasPermission(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "target": "es2017", 9 | "sourceMap": true, 10 | "outDir": "./dist", 11 | "baseUrl": "./", 12 | "incremental": true 13 | }, 14 | "exclude": ["node_modules", "dist"] 15 | } 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "quotemark": [true, "single"], 9 | "member-access": [false], 10 | "ordered-imports": [false], 11 | "max-line-length": [true, 150], 12 | "member-ordering": [false], 13 | "interface-name": [false], 14 | "arrow-parens": false, 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } 19 | --------------------------------------------------------------------------------