├── .editorconfig ├── .env ├── .github └── workflows │ └── ci-test.yml ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc ├── .npmignore ├── .npmrc ├── .prettierrc ├── LICENSE.md ├── README.md ├── apps ├── biometric │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.biometric.controller.ts │ │ │ ├── auth.constants.ts │ │ │ ├── auth.entity.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ ├── index.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ ├── interfaces │ │ │ │ ├── auth.ts │ │ │ │ ├── index.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ └── strategies │ │ │ │ ├── biometric.ts │ │ │ │ ├── index.ts │ │ │ │ └── jwt.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── biometric.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ ├── 1563804021014-seed-users.ts │ │ │ └── 1572880566396-create-auth-table.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── dto │ │ │ ├── create.ts │ │ │ └── index.ts │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ └── index.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── firebase │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.ts │ │ │ └── strategies │ │ │ │ ├── firebase.ts │ │ │ │ ├── firebase.ws.ts │ │ │ │ └── index.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── firebase.ts │ │ │ │ ├── firebase.ws.ts │ │ │ │ ├── index.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── events │ │ │ ├── decorators │ │ │ │ └── user.ts │ │ │ ├── event.gateway.ts │ │ │ └── event.module.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ └── 1563804021014-seed-users.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ ├── index.html │ │ └── socket.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── jwks-rest │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.module.ts │ │ │ └── strategies │ │ │ │ ├── apple.ts │ │ │ │ ├── auth0.ts │ │ │ │ ├── cognito.ts │ │ │ │ ├── google.ts │ │ │ │ └── index.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ └── guards │ │ │ │ ├── apple.ts │ │ │ │ ├── auth0.ts │ │ │ │ ├── cognito.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ └── roles.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ └── 1563804021014-seed-users.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── interfaces │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── jwt-gql │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── schema.gql │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── auth.entity.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.resolver.spec.ts │ │ │ ├── auth.resolver.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── interfaces │ │ │ │ ├── auth.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ ├── strategies │ │ │ │ ├── index.ts │ │ │ │ └── jwt.ts │ │ │ └── types │ │ │ │ ├── index.ts │ │ │ │ └── jwt.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ ├── 1563804021014-seed-users.ts │ │ │ └── 1572880566396-create-auth-table.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── types │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── list.ts │ │ │ └── user.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.resolver.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── jwt-rest │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── auth.entity.ts │ │ │ ├── auth.jwt.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── auth.social.controller.ts │ │ │ ├── dto │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ ├── interfaces │ │ │ │ ├── auth.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ └── strategies │ │ │ │ ├── facebook.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ └── jwt.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── facebook.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ ├── 1563804021014-seed-users.ts │ │ │ └── 1572880566396-create-auth-table.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── dto │ │ │ ├── create.ts │ │ │ └── index.ts │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ └── index.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── jwt-ws │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── auth.entity.ts │ │ │ ├── auth.jwt.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ ├── interfaces │ │ │ │ ├── auth.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ └── strategies │ │ │ │ ├── index.ts │ │ │ │ └── jwt.ts │ │ ├── common │ │ │ ├── adapters │ │ │ │ └── redis-io.ts │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── events │ │ │ ├── decorators │ │ │ │ └── user.ts │ │ │ ├── event.gateway.ts │ │ │ ├── event.module.ts │ │ │ └── guards │ │ │ │ └── jwt.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ ├── 1563804021014-seed-users.ts │ │ │ └── 1572880566396-create-auth-table.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── dto │ │ │ ├── create.ts │ │ │ └── index.ts │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ ├── favicon.ico │ │ ├── index.html │ │ └── socket.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── metamask │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.constants.ts │ │ │ ├── auth.entity.ts │ │ │ ├── auth.metamask.controller.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── dto │ │ │ │ ├── index.ts │ │ │ │ ├── logout.ts │ │ │ │ └── refresh.ts │ │ │ ├── interfaces │ │ │ │ ├── auth.ts │ │ │ │ ├── index.ts │ │ │ │ ├── logout.ts │ │ │ │ ├── metamask.ts │ │ │ │ └── refresh.ts │ │ │ └── strategies │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ └── metamask.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── index.ts │ │ │ │ ├── jwt.ts │ │ │ │ ├── metamask.ts │ │ │ │ └── roles.ts │ │ │ └── jwt.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ ├── 1563804021014-seed-users.ts │ │ │ └── 1572880566396-create-auth-table.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ └── index.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── session-rest │ ├── .env │ ├── .npmignore │ ├── @types │ │ └── passport-openidconnect │ │ │ └── index.d.ts │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.module.ts │ │ │ ├── auth.service.spec.ts │ │ │ ├── auth.service.ts │ │ │ ├── auth.session.controller.ts │ │ │ ├── auth.social.controller.ts │ │ │ ├── session.serializer.ts │ │ │ └── strategies │ │ │ │ ├── facebook.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ ├── local.ts │ │ │ │ └── onelogin.ts │ │ ├── common │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ ├── index.ts │ │ │ │ ├── public.ts │ │ │ │ ├── roles.ts │ │ │ │ └── user.ts │ │ │ ├── guards │ │ │ │ ├── facebook.ts │ │ │ │ ├── google.ts │ │ │ │ ├── index.ts │ │ │ │ ├── local.ts │ │ │ │ ├── login.ts │ │ │ │ ├── onelogin.ts │ │ │ │ └── roles.ts │ │ │ └── middlewares │ │ │ │ └── session.ts │ │ ├── index.ts │ │ ├── migrations │ │ │ ├── 1561991006215-create-schema.ts │ │ │ ├── 1562222612033-create-user-table.ts │ │ │ └── 1563804021014-seed-users.ts │ │ ├── ormconfig.ts │ │ └── user │ │ │ ├── dto │ │ │ ├── create.ts │ │ │ └── index.ts │ │ │ ├── interfaces │ │ │ ├── create.ts │ │ │ ├── index.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ │ ├── user.controller.ts │ │ │ ├── user.entity.ts │ │ │ ├── user.module.ts │ │ │ ├── user.service.spec.ts │ │ │ └── user.service.ts │ ├── static │ │ └── index.html │ ├── tsconfig.eslint.json │ └── tsconfig.json └── session-ws │ ├── .env │ ├── .npmignore │ ├── README.md │ ├── jest.config.json │ ├── package.json │ ├── src │ ├── app.module.ts │ ├── auth │ │ ├── auth.controller.ts │ │ ├── auth.module.ts │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── session.serializer.ts │ │ └── strategies │ │ │ ├── index.ts │ │ │ └── local.ts │ ├── common │ │ ├── adapters │ │ │ └── redis-io.ts │ │ ├── constants.ts │ │ ├── decorators │ │ │ ├── index.ts │ │ │ ├── public.ts │ │ │ ├── roles.ts │ │ │ └── user.ts │ │ ├── guards │ │ │ ├── index.ts │ │ │ ├── local.ts │ │ │ ├── login.ts │ │ │ └── roles.ts │ │ └── middlewares │ │ │ └── session.ts │ ├── events │ │ ├── decorators │ │ │ ├── session.ts │ │ │ └── user.ts │ │ ├── event.gateway.ts │ │ ├── event.module.ts │ │ ├── guards │ │ │ └── local.ws.ts │ │ └── interceptors │ │ │ └── session.ts │ ├── index.ts │ ├── migrations │ │ ├── 1561991006215-create-schema.ts │ │ ├── 1562222612033-create-user-table.ts │ │ └── 1563804021014-seed-users.ts │ ├── ormconfig.ts │ └── user │ │ ├── dto │ │ ├── create.ts │ │ └── index.ts │ │ ├── interfaces │ │ ├── create.ts │ │ ├── index.ts │ │ ├── roles.ts │ │ └── user.ts │ │ ├── user.controller.ts │ │ ├── user.entity.ts │ │ ├── user.module.ts │ │ ├── user.service.spec.ts │ │ └── user.service.ts │ ├── static │ ├── favicon.ico │ ├── index.html │ └── socket.html │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── docker-compose.yml ├── eslint.config.mjs ├── lerna.json ├── package.json ├── renovate.json ├── scripts ├── clean.sh └── test.sh ├── tsconfig.eslint.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BASE_PATH=. 2 | BASE_MODE=prod 3 | NODE_ENV=production 4 | HOST=0.0.0.0 5 | 6 | # DB 7 | POSTGRES_DB=postgres 8 | POSTGRES_HOST=postgres 9 | POSTGRES_USER=postgres 10 | POSTGRES_PASSWORD=password 11 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 12 | -------------------------------------------------------------------------------- /.github/workflows/ci-test.yml: -------------------------------------------------------------------------------- 1 | name: CI Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | services: 15 | postgres: 16 | # Docker Hub image 17 | image: postgres:alpine 18 | # Provide the password for postgres 19 | env: 20 | POSTGRES_PASSWORD: postgres 21 | POSTGRES_USER: postgres 22 | POSTGRES_DB: test-db 23 | POSTGRES_PORT: 5432 24 | ports: ["5432:5432"] 25 | # Set health checks to wait until postgres has started 26 | options: >- 27 | --health-cmd pg_isready 28 | --health-interval 10s 29 | --health-timeout 5s 30 | --health-retries 5 31 | 32 | steps: 33 | - name: Use Node.js 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: "22.x" 37 | - name: checkout 38 | uses: actions/checkout@v4 39 | with: 40 | ref: ${{ github.head_ref }} 41 | 42 | - name: Install Packages 43 | run: npm i 44 | 45 | - name: Npm run test 46 | run: npm run test 47 | env: 48 | NODE_ENV: test 49 | POSTGRES_URL: "postgres://postgres:postgres@0.0.0.0:5432/test-db" 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project 2 | dist/ 3 | 4 | # IDE 5 | .idea 6 | .vscode 7 | 8 | # OS 9 | .DS_Store 10 | 11 | # NODE 12 | node_modules/ 13 | npm-debug.log 14 | package-lock.json 15 | yarn.lock 16 | report.*.json 17 | 18 | # CONFIG 19 | service-account-file.json 20 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | lint-staged 2 | -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"], 3 | "*.json": ["prettier --write"] 4 | } 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /eslint.config.mjs 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | package-lock=false 3 | legacy-peer-deps=true 4 | 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "printWidth": 120, 5 | "trailingComma": "all", 6 | "singleQuote": false 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 EthBerry Studio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /apps/biometric/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH 9 | JWT_SECRET_KEY=keyboard_cat 10 | -------------------------------------------------------------------------------- /apps/biometric/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/biometric/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { JwtModule } from "@nestjs/jwt"; 4 | import { TypeOrmModule } from "@nestjs/typeorm"; 5 | import { ConfigModule, ConfigService } from "@nestjs/config"; 6 | 7 | import { AuthService } from "./auth.service"; 8 | import { AuthEntity } from "./auth.entity"; 9 | import { UserModule } from "../user/user.module"; 10 | import { BiometricStrategy, JwtStrategy } from "./strategies"; 11 | import { accessTokenExpiresIn } from "./auth.constants"; 12 | import { AuthBiometricController } from "./auth.biometric.controller"; 13 | 14 | @Module({ 15 | imports: [ 16 | TypeOrmModule.forFeature([AuthEntity]), 17 | UserModule, 18 | PassportModule, 19 | ConfigModule, 20 | JwtModule.registerAsync({ 21 | imports: [ConfigModule], 22 | inject: [ConfigService], 23 | useFactory: (configService: ConfigService) => ({ 24 | secret: configService.get("JWT_SECRET_KEY"), 25 | signOptions: { 26 | expiresIn: accessTokenExpiresIn, 27 | }, 28 | }), 29 | }), 30 | ], 31 | controllers: [AuthBiometricController], 32 | providers: [AuthService, JwtStrategy, BiometricStrategy], 33 | exports: [AuthService], 34 | }) 35 | export class AuthModule {} 36 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./logout"; 2 | export * from "./refresh"; 3 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/dto/logout.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { ILogoutDto } from "../interfaces"; 6 | 7 | export class JwtLogoutDto implements ILogoutDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/dto/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { IRefreshDto } from "../interfaces"; 6 | 7 | export class JwtRefreshTokenDto implements IRefreshDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "../../user/interfaces"; 2 | 3 | export interface IAuth { 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | user?: IUser; 7 | userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./logout"; 3 | export * from "./refresh"; 4 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/interfaces/logout.ts: -------------------------------------------------------------------------------- 1 | export interface ILogoutDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/interfaces/refresh.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./biometric"; 2 | export * from "./jwt"; 3 | -------------------------------------------------------------------------------- /apps/biometric/src/auth/strategies/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserService } from "../../user/user.service"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy, "jwt") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: process.env.JWT_SECRET_KEY!, 15 | }); 16 | } 17 | 18 | public async validate(payload: { email: string }): Promise { 19 | const userEntity = await this.userService.findOne({ email: payload.email }); 20 | 21 | if (!userEntity) { 22 | throw new UnauthorizedException(); 23 | } 24 | 25 | return userEntity; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/biometric/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "biometric"; 2 | -------------------------------------------------------------------------------- /apps/biometric/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/biometric/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/biometric/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/biometric/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user as IUser; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/biometric/src/common/guards/biometric.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class BiometricGuard extends AuthGuard("biometric") implements CanActivate {} 6 | -------------------------------------------------------------------------------- /apps/biometric/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./biometric"; 2 | export * from "./jwt"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/biometric/src/common/guards/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class JwtGuard extends AuthGuard("jwt") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/biometric/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | 5 | import { UserRole } from "../../user/interfaces"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class RolesGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.getAllAndOverride>("roles", [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | 18 | if (!roles?.length) { 19 | return true; 20 | } 21 | 22 | const request = context.switchToHttp().getRequest(); 23 | const userEntity = request.user as UserEntity; 24 | 25 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 26 | 27 | if (hasRole) { 28 | return true; 29 | } 30 | 31 | throw new UnauthorizedException("userHasWrongRole"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/biometric/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/biometric/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | import { ConfigService } from "@nestjs/config"; 5 | import passport from "passport"; 6 | 7 | import { AppModule } from "./app.module"; 8 | 9 | async function bootstrap(): Promise { 10 | const app = await NestFactory.create(AppModule); 11 | 12 | const configService = app.get(ConfigService); 13 | 14 | app.use(passport.initialize()); 15 | 16 | const options = new DocumentBuilder() 17 | .addBearerAuth() 18 | .setTitle("biometric") 19 | .setDescription("API description") 20 | .setVersion("1.0") 21 | .build(); 22 | const document = SwaggerModule.createDocument(app, options); 23 | SwaggerModule.setup("swagger", app, document); 24 | 25 | const host = configService.get("HOST", "localhost"); 26 | const port = configService.get("PORT", 3000); 27 | 28 | await app.listen(port, host, () => { 29 | console.info(`Express server is running on http://${host}:${port}/`); 30 | }); 31 | } 32 | 33 | void bootstrap(); 34 | -------------------------------------------------------------------------------- /apps/biometric/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/biometric/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "email", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "biometric_public_key", 28 | type: "varchar", 29 | isNullable: true, 30 | }, 31 | { 32 | name: "roles", 33 | type: `${ns}.user_role_enum`, 34 | isArray: true, 35 | }, 36 | ], 37 | }); 38 | 39 | await queryRunner.createTable(table, true); 40 | } 41 | 42 | public async down(queryRunner: QueryRunner): Promise { 43 | await queryRunner.dropTable(`${ns}.user`); 44 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /apps/biometric/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const biometricPublicKey = 8 | "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyiLoIn" + 9 | "CN1qeo1k3JeEjpMKpbpZhiwSOMFQ9PgA0xFNK1EHh5sGdfwiVc" + 10 | "Xc5JV8ciXJLjbXO+4W5WWlKz26K3Cl3zLn92kQCkEnxhO2aqS9" + 11 | "OZ4AIwo0/ww7+QD6q57/UJFsqaTHAdetb2E3jrDDFZX5kegAV+" + 12 | "xXh38+nP8Zopx/A8DXI6P9FKBwpZredRqd3gw3j6bNiEmGW9wZ" + 13 | "CU3kghi7u3bWbjkmrN35Sg8CGnx2r0krAskx4I5JMKeYetr4fK" + 14 | "xxoArF9XJHl5gi/2aUk342iFTbZtRKIRbgFkeeUqTw7e5QsSFl" + 15 | "AFGIWkIq1QSveH0PnFe2nidZHpGKWeiSsqGQIDAQAB"; 16 | 17 | await queryRunner.query(` 18 | INSERT INTO ${ns}.user ( 19 | email, 20 | biometric_public_key, 21 | roles 22 | ) VALUES ( 23 | 'trejgun@gmail.com', 24 | '${biometricPublicKey}', 25 | '{ADMIN}' 26 | ); 27 | `); 28 | } 29 | 30 | public async down(queryRunner: QueryRunner): Promise { 31 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/biometric/src/user/dto/create.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsEmail, IsString } from "class-validator"; 3 | 4 | import { IUserCreateDto } from "../interfaces"; 5 | 6 | export class UserCreateDto implements IUserCreateDto { 7 | @ApiProperty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @ApiProperty() 12 | @IsString() 13 | public biometricPublicKey: string; 14 | } 15 | -------------------------------------------------------------------------------- /apps/biometric/src/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | -------------------------------------------------------------------------------- /apps/biometric/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | biometricPublicKey: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/biometric/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | export * from "./roles"; 3 | export * from "./user"; 4 | -------------------------------------------------------------------------------- /apps/biometric/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/biometric/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | biometricPublicKey: string; 7 | roles: Array; 8 | } 9 | -------------------------------------------------------------------------------- /apps/biometric/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | import { ApiBearerAuth } from "@nestjs/swagger"; 3 | 4 | import { UserEntity } from "./user.entity"; 5 | import { Roles, User } from "../common/decorators"; 6 | import { UserRole } from "./interfaces"; 7 | import { UserService } from "./user.service"; 8 | 9 | @ApiBearerAuth() 10 | @Controller("/users") 11 | export class UserController { 12 | constructor(private readonly userService: UserService) {} 13 | 14 | @Get("/profile") 15 | @UseInterceptors(ClassSerializerInterceptor) 16 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 17 | return userEntity; 18 | } 19 | 20 | @Get("/") 21 | @Roles(UserRole.ADMIN) 22 | @UseInterceptors(ClassSerializerInterceptor) 23 | public findAll(): Promise<{ rows: Array; count: number }> { 24 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/biometric/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { Exclude } from "class-transformer"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { IUser, UserRole } from "./interfaces"; 6 | 7 | @Entity({ schema: ns, name: "user" }) 8 | export class UserEntity extends BaseEntity implements IUser { 9 | @PrimaryGeneratedColumn() 10 | public id: number; 11 | 12 | @Column({ type: "varchar" }) 13 | public email: string; 14 | 15 | @Exclude() 16 | @Column({ type: "varchar", nullable: true }) 17 | public biometricPublicKey: string; 18 | 19 | @Column({ 20 | type: "enum", 21 | enum: UserRole, 22 | array: true, 23 | }) 24 | public roles: Array; 25 | } 26 | -------------------------------------------------------------------------------- /apps/biometric/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | controllers: [UserController], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/biometric/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Repository, FindOptionsWhere } from "typeorm"; 2 | 3 | import { Injectable, ConflictException } from "@nestjs/common"; 4 | import { InjectRepository } from "@nestjs/typeorm"; 5 | 6 | import { UserEntity } from "./user.entity"; 7 | import { IUserCreateDto, UserRole } from "./interfaces"; 8 | 9 | @Injectable() 10 | export class UserService { 11 | constructor( 12 | @InjectRepository(UserEntity) 13 | private readonly userEntityRepository: Repository, 14 | ) {} 15 | 16 | public findOne(where: FindOptionsWhere): Promise { 17 | return this.userEntityRepository.findOne({ where }); 18 | } 19 | 20 | public findAndCount(): Promise<[Array, number]> { 21 | return this.userEntityRepository.findAndCount(); 22 | } 23 | 24 | public async create(dto: IUserCreateDto): Promise { 25 | const userEntity = await this.findOne({ email: dto.email }); 26 | 27 | if (userEntity) { 28 | throw new ConflictException(); 29 | } 30 | 31 | return this.userEntityRepository 32 | .create({ 33 | ...dto, 34 | roles: [UserRole.USER], 35 | }) 36 | .save(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/biometric/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Have to figure our a way to set up all these 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/biometric/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/biometric/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/firebase/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH 9 | JWT_SECRET_KEY=keyboard_cat 10 | 11 | # GOOGLE 12 | GOOGLE_APPLICATION_CREDENTIALS=./service-account-file.json 13 | -------------------------------------------------------------------------------- /apps/firebase/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/firebase/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/firebase/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | 4 | export const APP_PROVIDER = Symbol("APP_PROVIDER"); 5 | -------------------------------------------------------------------------------- /apps/firebase/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from "@nestjs/common"; 2 | import { app } from "firebase-admin"; 3 | 4 | import { UserEntity } from "../user/user.entity"; 5 | import { APP_PROVIDER } from "./auth.constants"; 6 | 7 | @Injectable() 8 | export class AuthService { 9 | constructor( 10 | @Inject(APP_PROVIDER) 11 | private readonly admin: app.App, 12 | ) {} 13 | 14 | public delete(userEntity: UserEntity): Promise { 15 | return this.admin.auth().deleteUser(userEntity.sub); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/firebase/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./firebase"; 2 | export * from "./firebase.ws"; 3 | -------------------------------------------------------------------------------- /apps/firebase/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "firebase"; 2 | -------------------------------------------------------------------------------- /apps/firebase/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/firebase/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/firebase/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/firebase/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user as IUser; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/firebase/src/common/guards/firebase.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class FirebaseGuard extends AuthGuard("firebase") implements CanActivate {} 6 | -------------------------------------------------------------------------------- /apps/firebase/src/common/guards/firebase.ws.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | 5 | @Injectable() 6 | export class FirebaseWsGuard extends AuthGuard("firebase-ws") implements CanActivate { 7 | constructor(private readonly reflector: Reflector) { 8 | super(); 9 | } 10 | 11 | public async canActivate(context: ExecutionContext): Promise { 12 | // `super` has to be called to set `user` on `request` 13 | // see https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts 14 | return (super.canActivate(context) as Promise).catch(e => { 15 | const isPublic = this.reflector.getAllAndOverride("isPublic", [ 16 | context.getHandler(), 17 | context.getClass(), 18 | ]); 19 | 20 | if (isPublic) { 21 | return true; 22 | } 23 | 24 | console.error(e); 25 | throw new UnauthorizedException("unauthorized"); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/firebase/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./firebase"; 2 | export * from "./firebase.ws"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/firebase/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | 5 | import { UserRole } from "../../user/interfaces"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class RolesGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.getAllAndOverride>("roles", [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | 18 | if (!roles?.length) { 19 | return true; 20 | } 21 | 22 | const request = context.switchToHttp().getRequest(); 23 | const userEntity = request.user as UserEntity; 24 | 25 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 26 | 27 | if (hasRole) { 28 | return true; 29 | } 30 | 31 | throw new UnauthorizedException("userHasWrongRole"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/firebase/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/firebase/src/events/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/firebase/src/events/event.gateway.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Logger, LoggerService, UseGuards } from "@nestjs/common"; 2 | import { OnGatewayInit, SubscribeMessage, WebSocketGateway, WebSocketServer } from "@nestjs/websockets"; 3 | import { Server } from "socket.io"; 4 | import { User } from "./decorators/user"; 5 | import { UserEntity } from "../user/user.entity"; 6 | import { FirebaseWsGuard } from "../common/guards"; 7 | 8 | @UseGuards(FirebaseWsGuard) 9 | @WebSocketGateway() 10 | export class EventGateway implements OnGatewayInit { 11 | @WebSocketServer() 12 | server: Server; 13 | 14 | constructor(@Inject(Logger) private readonly loggerService: LoggerService) {} 15 | 16 | @SubscribeMessage("profile") 17 | plain(@User() userEntity: UserEntity): UserEntity { 18 | return userEntity; 19 | } 20 | 21 | public afterInit(): void { 22 | this.loggerService.log("Init", EventGateway.name); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/firebase/src/events/event.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Logger } from "@nestjs/common"; 2 | 3 | import { EventGateway } from "./event.gateway"; 4 | 5 | @Module({ 6 | providers: [Logger, EventGateway], 7 | }) 8 | export class EventModule {} 9 | -------------------------------------------------------------------------------- /apps/firebase/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | import { ConfigService } from "@nestjs/config"; 5 | import passport from "passport"; 6 | 7 | import { AppModule } from "./app.module"; 8 | 9 | async function bootstrap(): Promise { 10 | const app = await NestFactory.create(AppModule); 11 | 12 | const configService = app.get(ConfigService); 13 | 14 | app.use(passport.initialize()); 15 | 16 | const options = new DocumentBuilder() 17 | .addBearerAuth() 18 | .setTitle("jwt-rest") 19 | .setDescription("API description") 20 | .setVersion("1.0") 21 | .build(); 22 | const document = SwaggerModule.createDocument(app, options); 23 | SwaggerModule.setup("swagger", app, document); 24 | 25 | const host = configService.get("HOST", "localhost"); 26 | const port = configService.get("PORT", 3000); 27 | 28 | await app.listen(port, host, () => { 29 | console.info(`Express server is running on http://${host}:${port}/`); 30 | }); 31 | } 32 | 33 | void bootstrap(); 34 | -------------------------------------------------------------------------------- /apps/firebase/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/firebase/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "sub", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "roles", 28 | type: `${ns}.user_role_enum`, 29 | isArray: true, 30 | }, 31 | ], 32 | }); 33 | 34 | await queryRunner.createTable(table, true); 35 | } 36 | 37 | public async down(queryRunner: QueryRunner): Promise { 38 | await queryRunner.dropTable(`${ns}.user`); 39 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/firebase/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | INSERT INTO ${ns}.user ( 9 | sub, 10 | roles 11 | ) VALUES ( 12 | 'w8xC5gBdzSRi0LlBfRZqzxqzKe13', 13 | '{ADMIN}' 14 | ); 15 | `); 16 | } 17 | 18 | public async down(queryRunner: QueryRunner): Promise { 19 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/firebase/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | sub: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/firebase/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/firebase/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/firebase/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | sub: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/firebase/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors, Delete, HttpCode, Param } from "@nestjs/common"; 2 | import { ApiBearerAuth } from "@nestjs/swagger"; 3 | 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserEntity } from "./user.entity"; 6 | import { UserRole } from "./interfaces"; 7 | import { UserService } from "./user.service"; 8 | 9 | @ApiBearerAuth() 10 | @Controller("/users") 11 | export class UserController { 12 | constructor(private readonly userService: UserService) {} 13 | 14 | @Get("/profile") 15 | @UseInterceptors(ClassSerializerInterceptor) 16 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 17 | return userEntity; 18 | } 19 | 20 | @Get("/") 21 | @Roles(UserRole.ADMIN) 22 | @UseInterceptors(ClassSerializerInterceptor) 23 | public findAll(): Promise<{ rows: Array; count: number }> { 24 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 25 | } 26 | 27 | @Delete("/:id") 28 | @HttpCode(204) 29 | @Roles(UserRole.ADMIN) 30 | public async delete(@Param("id") id: number): Promise { 31 | await this.userService.delete({ id }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/firebase/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | import { IUser, UserRole } from "./interfaces"; 5 | 6 | @Entity({ schema: ns, name: "user" }) 7 | export class UserEntity extends BaseEntity implements IUser { 8 | @PrimaryGeneratedColumn() 9 | public id: number; 10 | 11 | @Column({ type: "varchar" }) 12 | public sub: string; 13 | 14 | @Column({ 15 | type: "enum", 16 | enum: UserRole, 17 | array: true, 18 | }) 19 | public roles: Array; 20 | } 21 | -------------------------------------------------------------------------------- /apps/firebase/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, forwardRef } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | 5 | import { UserController } from "./user.controller"; 6 | import { UserEntity } from "./user.entity"; 7 | import { AuthModule } from "../auth/auth.module"; 8 | 9 | @Module({ 10 | imports: [TypeOrmModule.forFeature([UserEntity]), forwardRef(() => AuthModule)], 11 | providers: [UserService], 12 | controllers: [UserController], 13 | exports: [UserService], 14 | }) 15 | export class UserModule {} 16 | -------------------------------------------------------------------------------- /apps/firebase/static/socket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test 4 | 5 | 31 | 32 | 33 |

Open console and press the button

34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /apps/firebase/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/firebase/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwks-rest/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH0 9 | AUTH0_ISSUER_URL=https://.auth0.com/ 10 | AUTH0_AUDIENCE=https://example.com/ 11 | 12 | # COGNITO 13 | COGNITO_CLIENT_ID= 14 | COGNITO_CLIENT_SECRET= 15 | COGNITO_USER_POOL_ID=s-west-1_XXXXX 16 | COGNITO_REGION=us-west-1 17 | COGNITO_ISSUER_URL=https://cognito-idp..amazonaws.com/ 18 | -------------------------------------------------------------------------------- /apps/jwks-rest/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/jwks-rest/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { ConfigModule } from "@nestjs/config"; 4 | 5 | import { UserModule } from "../user/user.module"; 6 | import { AppleStrategy, Auth0Strategy, CognitoStrategy, GoogleStrategy } from "./strategies"; 7 | 8 | @Module({ 9 | imports: [UserModule, PassportModule, ConfigModule], 10 | providers: [AppleStrategy, Auth0Strategy, GoogleStrategy, CognitoStrategy], 11 | }) 12 | export class AuthModule {} 13 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/auth/strategies/apple.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | import { passportJwtSecret } from "jwks-rsa"; 5 | 6 | import { UserService } from "../../user/user.service"; 7 | import { UserEntity } from "../../user/user.entity"; 8 | 9 | @Injectable() 10 | export class AppleStrategy extends PassportStrategy(Strategy, "apple") { 11 | constructor(private readonly userService: UserService) { 12 | super({ 13 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 14 | secretOrKeyProvider: passportJwtSecret({ 15 | cache: true, 16 | rateLimit: true, 17 | jwksRequestsPerMinute: 5, 18 | jwksUri: "https://appleid.apple.com/auth/keys", 19 | }), 20 | issuer: "https://appleid.apple.com", 21 | algorithms: ["RS256"], 22 | }); 23 | } 24 | 25 | public async validate(payload: { email: string }): Promise { 26 | const userEntity = await this.userService.findOne({ email: payload.email }); 27 | 28 | if (!userEntity) { 29 | throw new UnauthorizedException(); 30 | } 31 | 32 | return userEntity; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/auth/strategies/google.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | import { passportJwtSecret } from "jwks-rsa"; 5 | 6 | import { UserService } from "../../user/user.service"; 7 | import { UserEntity } from "../../user/user.entity"; 8 | 9 | @Injectable() 10 | export class GoogleStrategy extends PassportStrategy(Strategy, "google") { 11 | constructor(private readonly userService: UserService) { 12 | super({ 13 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 14 | secretOrKeyProvider: passportJwtSecret({ 15 | cache: true, 16 | rateLimit: true, 17 | jwksRequestsPerMinute: 5, 18 | jwksUri: "https://www.googleapis.com/oauth2/v3/certs", 19 | }), 20 | issuer: "https://accounts.google.com", 21 | algorithms: ["RS256"], 22 | }); 23 | } 24 | 25 | public async validate(payload: { email: string }): Promise { 26 | const userEntity = await this.userService.findOne({ email: payload.email }); 27 | 28 | if (!userEntity) { 29 | throw new UnauthorizedException(); 30 | } 31 | 32 | return userEntity; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./apple"; 2 | export * from "./auth0"; 3 | export * from "./cognito"; 4 | export * from "./google"; 5 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "jwks_rest"; 2 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/apple.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class AppleGuard extends AuthGuard("apple") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/auth0.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class Auth0Guard extends AuthGuard("auth0") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/cognito.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class CognitoGuard extends AuthGuard("cognito") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/google.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class GoogleGuard extends AuthGuard("google") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./apple"; 2 | export * from "./auth0"; 3 | export * from "./cognito"; 4 | export * from "./google"; 5 | export * from "./roles"; 6 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | 5 | import { UserRole } from "../../user/interfaces"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class RolesGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.getAllAndOverride>("roles", [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | 18 | if (!roles?.length) { 19 | return true; 20 | } 21 | 22 | const request = context.switchToHttp().getRequest(); 23 | const userEntity = request.user as UserEntity; 24 | 25 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 26 | 27 | if (hasRole) { 28 | return true; 29 | } 30 | 31 | throw new UnauthorizedException("userHasWrongRole"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { ConfigService } from "@nestjs/config"; 3 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 4 | import { NestExpressApplication } from "@nestjs/platform-express"; 5 | 6 | import { AppModule } from "./app.module"; 7 | 8 | async function bootstrap(): Promise { 9 | const app = await NestFactory.create(AppModule); 10 | 11 | const configService = app.get(ConfigService); 12 | 13 | const options = new DocumentBuilder() 14 | .setTitle("jwks-rest") 15 | .setDescription("API description") 16 | .setVersion("1.0") 17 | .build(); 18 | const document = SwaggerModule.createDocument(app, options); 19 | SwaggerModule.setup("swagger", app, document); 20 | 21 | const host = configService.get("HOST", "localhost"); 22 | const port = configService.get("PORT", 3000); 23 | 24 | await app.listen(port, host, () => { 25 | console.info(`Express server is running on http://${host}:${port}/`); 26 | }); 27 | } 28 | 29 | void bootstrap(); 30 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | import { ns } from "../common/constants"; 3 | 4 | export class CreateUserTable1562222612033 implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(` 7 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 8 | 'ADMIN', 9 | 'USER' 10 | ); 11 | `); 12 | 13 | const table = new Table({ 14 | name: `${ns}.user`, 15 | columns: [ 16 | { 17 | name: "id", 18 | type: "serial", 19 | isPrimary: true, 20 | }, 21 | { 22 | name: "email", 23 | type: "varchar", 24 | }, 25 | { 26 | name: "roles", 27 | type: `${ns}.user_role_enum`, 28 | isArray: true, 29 | }, 30 | ], 31 | }); 32 | 33 | await queryRunner.createTable(table, true); 34 | } 35 | 36 | public async down(queryRunner: QueryRunner): Promise { 37 | await queryRunner.dropTable(`${ns}.user`); 38 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | INSERT INTO ${ns}.user ( 9 | email, 10 | roles 11 | ) VALUES ( 12 | 'trejgun@gmail.com', 13 | '{ADMIN}' 14 | ); 15 | `); 16 | } 17 | 18 | public async down(queryRunner: QueryRunner): Promise { 19 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | 3 | import { UserEntity } from "./user.entity"; 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserRole } from "./interfaces"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Controller("/users") 9 | export class UserController { 10 | constructor(private readonly userService: UserService) {} 11 | 12 | @Get("/profile") 13 | public getProfile(@User() userEntity: UserEntity): UserEntity { 14 | return userEntity; 15 | } 16 | 17 | @Get("/") 18 | @Roles(UserRole.ADMIN) 19 | @UseInterceptors(ClassSerializerInterceptor) 20 | public findAll(): Promise<{ rows: Array; count: number }> { 21 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | import { IUser, UserRole } from "./interfaces"; 5 | 6 | @Entity({ schema: ns, name: "user" }) 7 | export class UserEntity extends BaseEntity implements IUser { 8 | @PrimaryGeneratedColumn() 9 | public id: number; 10 | 11 | @Column({ type: "varchar" }) 12 | public email: string; 13 | 14 | @Column({ 15 | type: "enum", 16 | enum: UserRole, 17 | array: true, 18 | }) 19 | public roles: Array; 20 | } 21 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | controllers: [UserController], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/jwks-rest/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { FindOptionsWhere, Repository } from "typeorm"; 2 | 3 | import { Injectable } from "@nestjs/common"; 4 | import { InjectRepository } from "@nestjs/typeorm"; 5 | 6 | import { UserEntity } from "./user.entity"; 7 | 8 | @Injectable() 9 | export class UserService { 10 | constructor( 11 | @InjectRepository(UserEntity) 12 | private readonly userEntityRepository: Repository, 13 | ) {} 14 | 15 | public findOne(where: FindOptionsWhere): Promise { 16 | return this.userEntityRepository.findOne({ where }); 17 | } 18 | 19 | public findAndCount(): Promise<[Array, number]> { 20 | return this.userEntityRepository.findAndCount(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/jwks-rest/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwks-rest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDirs": ["./src"], 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_module", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwt-gql/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH 9 | JWT_SECRET_KEY=keyboard_cat 10 | -------------------------------------------------------------------------------- /apps/jwt-gql/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/jwt-gql/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/jwt-gql/schema.gql: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------ 2 | # THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY) 3 | # ------------------------------------------------------ 4 | 5 | type UserListType { 6 | rows: [UserType!]! 7 | count: Float! 8 | } 9 | 10 | type UserType { 11 | id: Int! 12 | email: String! 13 | roles: [UserRole!]! 14 | } 15 | 16 | enum UserRole { 17 | USER 18 | ADMIN 19 | } 20 | 21 | type Jwt { 22 | accessToken: String! 23 | refreshToken: String! 24 | accessTokenExpiresAt: Float! 25 | refreshTokenExpiresAt: Float! 26 | } 27 | 28 | type Query { 29 | profile: UserType! 30 | listUsers: UserListType! 31 | } 32 | 33 | type Mutation { 34 | login(email: String!, password: String!): Jwt! 35 | refreshToken(refreshToken: String!): Jwt! 36 | logout(refreshToken: String!): Boolean! 37 | signup(input: UserCreateInputType!): Jwt! 38 | } 39 | 40 | input UserCreateInputType { 41 | email: String! 42 | password: String! 43 | roles: [UserRole!]! 44 | } 45 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/auth.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseEntity, 3 | BeforeInsert, 4 | BeforeUpdate, 5 | Column, 6 | Entity, 7 | JoinColumn, 8 | OneToOne, 9 | PrimaryGeneratedColumn, 10 | } from "typeorm"; 11 | 12 | import { ns } from "../common/constants"; 13 | import { UserEntity } from "../user/user.entity"; 14 | import { IAuth } from "./interfaces"; 15 | 16 | @Entity({ schema: ns, name: "auth" }) 17 | export class AuthEntity extends BaseEntity implements IAuth { 18 | @PrimaryGeneratedColumn() 19 | public id: number; 20 | 21 | @Column({ type: "varchar" }) 22 | public refreshToken: string; 23 | 24 | @Column({ type: "int" }) 25 | public refreshTokenExpiresAt: number; 26 | 27 | @JoinColumn() 28 | @OneToOne(_type => UserEntity) 29 | public user: UserEntity; 30 | 31 | @Column({ type: "int" }) 32 | public userId: number; 33 | 34 | @Column({ type: "timestamptz" }) 35 | public createdAt: string; 36 | 37 | @Column({ type: "timestamptz" }) 38 | public updatedAt: string; 39 | 40 | @BeforeInsert() 41 | public beforeInsert(): void { 42 | const date = new Date(); 43 | this.createdAt = date.toISOString(); 44 | this.updatedAt = date.toISOString(); 45 | } 46 | 47 | @BeforeUpdate() 48 | public beforeUpdate(): void { 49 | const date = new Date(); 50 | this.updatedAt = date.toISOString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { JwtModule } from "@nestjs/jwt"; 4 | import { TypeOrmModule } from "@nestjs/typeorm"; 5 | import { ConfigModule, ConfigService } from "@nestjs/config"; 6 | 7 | import { AuthService } from "./auth.service"; 8 | import { UserModule } from "../user/user.module"; 9 | import { AuthResolver } from "./auth.resolver"; 10 | import { AuthEntity } from "./auth.entity"; 11 | import { JwtStrategy } from "./strategies"; 12 | import { accessTokenExpiresIn } from "./auth.constants"; 13 | 14 | @Module({ 15 | imports: [ 16 | TypeOrmModule.forFeature([AuthEntity]), 17 | UserModule, 18 | PassportModule, 19 | JwtModule.registerAsync({ 20 | imports: [ConfigModule], 21 | inject: [ConfigService], 22 | useFactory: (configService: ConfigService) => ({ 23 | secret: configService.get("JWT_SECRET_KEY"), 24 | signOptions: { 25 | expiresIn: accessTokenExpiresIn, 26 | }, 27 | }), 28 | }), 29 | ConfigModule, 30 | ], 31 | providers: [AuthService, JwtStrategy, AuthResolver], 32 | exports: [AuthService], 33 | }) 34 | export class AuthModule {} 35 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "../../user/interfaces"; 2 | 3 | export interface IAuth { 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | user?: IUser; 7 | userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./login"; 3 | export * from "./logout"; 4 | export * from "./refresh"; 5 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/interfaces/login.ts: -------------------------------------------------------------------------------- 1 | export interface ILoginFields { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/interfaces/logout.ts: -------------------------------------------------------------------------------- 1 | export interface ILogoutFields { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/interfaces/refresh.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshFields { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/strategies/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserService } from "../../user/user.service"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy, "jwt") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: process.env.JWT_SECRET_KEY!, 15 | }); 16 | } 17 | 18 | public async validate(payload: { email: string }): Promise { 19 | const userEntity = await this.userService.findOne({ email: payload.email }); 20 | 21 | if (!userEntity) { 22 | throw new UnauthorizedException(); 23 | } 24 | 25 | return userEntity; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/auth/types/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from "@nestjs/graphql"; 2 | 3 | import { IJwt } from "../../common/jwt"; 4 | 5 | @ObjectType() 6 | export class Jwt implements IJwt { 7 | @Field() 8 | public accessToken: string; 9 | 10 | @Field() 11 | public refreshToken: string; 12 | 13 | @Field() 14 | public accessTokenExpiresAt: number; 15 | 16 | @Field() 17 | public refreshTokenExpiresAt: number; 18 | } 19 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "jwt_gql"; 2 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | import { GqlExecutionContext } from "@nestjs/graphql"; 3 | 4 | import { IUser } from "../../user/interfaces"; 5 | 6 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 7 | const context = GqlExecutionContext.create(ctx); 8 | return (context.getContext().req.user as IUser) || null; 9 | }); 10 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | export * from "./roles"; 3 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | import { GqlExecutionContext } from "@nestjs/graphql"; 5 | 6 | import { UserRole } from "../../user/interfaces"; 7 | import { UserEntity } from "../../user/user.entity"; 8 | 9 | @Injectable() 10 | export class RolesGuard implements CanActivate { 11 | constructor(private readonly reflector: Reflector) {} 12 | 13 | canActivate(context: ExecutionContext): boolean { 14 | const roles = this.reflector.getAllAndOverride>("roles", [ 15 | context.getHandler(), 16 | context.getClass(), 17 | ]); 18 | 19 | if (!roles?.length) { 20 | return true; 21 | } 22 | 23 | const ctx = GqlExecutionContext.create(context); 24 | const request = ctx.getContext().req as Request; 25 | 26 | const userEntity = request.user as UserEntity; 27 | 28 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 29 | 30 | if (hasRole) { 31 | return true; 32 | } 33 | 34 | throw new UnauthorizedException("userHasWrongRole"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/index.ts: -------------------------------------------------------------------------------- 1 | import passport from "passport"; 2 | import { NestFactory } from "@nestjs/core"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | 5 | import { AppModule } from "./app.module"; 6 | import { ConfigService } from "@nestjs/config"; 7 | 8 | async function bootstrap(): Promise { 9 | const app = await NestFactory.create(AppModule); 10 | 11 | const configService = app.get(ConfigService); 12 | 13 | app.use(passport.initialize()); 14 | 15 | const host = configService.get("HOST", "localhost"); 16 | const port = configService.get("PORT", 3000); 17 | 18 | await app.listen(port, host, () => { 19 | console.info(`Express server is running on http://${host}:${port}/`); 20 | 21 | if (process.env.NODE_ENV !== "production") { 22 | console.info(`GraphQL playground is at http://${host}:${port}/graphql`); 23 | } 24 | }); 25 | } 26 | 27 | void bootstrap(); 28 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | import { ns } from "../common/constants"; 3 | 4 | export class CreateUserTable1562222612033 implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | await queryRunner.query(` 7 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 8 | 'ADMIN', 9 | 'USER' 10 | ); 11 | `); 12 | 13 | const table = new Table({ 14 | name: `${ns}.user`, 15 | columns: [ 16 | { 17 | name: "id", 18 | type: "serial", 19 | isPrimary: true, 20 | }, 21 | { 22 | name: "email", 23 | type: "varchar", 24 | }, 25 | { 26 | name: "password", 27 | type: "varchar", 28 | }, 29 | { 30 | name: "roles", 31 | type: `${ns}.user_role_enum`, 32 | isArray: true, 33 | }, 34 | ], 35 | }); 36 | 37 | await queryRunner.createTable(table, true); 38 | } 39 | 40 | public async down(queryRunner: QueryRunner): Promise { 41 | await queryRunner.dropTable(`${ns}.user`); 42 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | import { ns } from "../common/constants"; 3 | 4 | export class SeedUsers1563804021014 implements MigrationInterface { 5 | public async up(queryRunner: QueryRunner): Promise { 6 | const passwordHash = "6c9311b5a0c96b76e6535d5c57a96d67a405779d2284aaf154148cdcbefc5af6"; // My5up3r5tr0ngP@55w0rd 7 | 8 | await queryRunner.query(` 9 | INSERT INTO ${ns}.user ( 10 | email, 11 | password, 12 | roles 13 | ) VALUES ( 14 | 'trejgun@gmail.com', 15 | '${passwordHash}', 16 | '{ADMIN}' 17 | ); 18 | `); 19 | } 20 | 21 | public async down(queryRunner: QueryRunner): Promise { 22 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | password: string; 7 | roles: Array; 8 | } 9 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/types/create.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from "@nestjs/graphql"; 2 | import { IsEmail, MinLength, IsEnum } from "class-validator"; 3 | 4 | import { IUserCreateDto, UserRole } from "../interfaces"; 5 | 6 | @InputType() 7 | export class UserCreateInputType implements IUserCreateDto { 8 | @Field() 9 | @IsEmail() 10 | public email: string; 11 | 12 | @Field() 13 | @MinLength(6) 14 | public password: string; 15 | 16 | @Field(_type => [UserRole]) 17 | @IsEnum(UserRole, { each: true }) 18 | public roles: Array; 19 | } 20 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | export * from "./list"; 3 | export * from "./user"; 4 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/types/list.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from "@nestjs/graphql"; 2 | import { UserType } from "."; 3 | 4 | @ObjectType() 5 | export class UserListType { 6 | @Field(_type => [UserType]) 7 | public rows: Array; 8 | 9 | @Field() 10 | public count: number; 11 | } 12 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/types/user.ts: -------------------------------------------------------------------------------- 1 | import { Field, Int, ObjectType, registerEnumType } from "@nestjs/graphql"; 2 | import { IUser, UserRole } from "../interfaces"; 3 | 4 | registerEnumType(UserRole, { 5 | name: "UserRole", 6 | }); 7 | 8 | @ObjectType() 9 | export class UserType implements IUser { 10 | @Field(_type => Int) 11 | public id: number; 12 | 13 | @Field() 14 | public email: string; 15 | 16 | public password: string; 17 | 18 | @Field(_type => [UserRole]) 19 | public roles: Array; 20 | } 21 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { registerEnumType } from "@nestjs/graphql"; 3 | import { Exclude } from "class-transformer"; 4 | 5 | import { ns } from "../common/constants"; 6 | import { IUser, UserRole } from "./interfaces"; 7 | 8 | registerEnumType(UserRole, { 9 | name: "UserRole", 10 | }); 11 | 12 | @Entity({ schema: ns, name: "user" }) 13 | export class UserEntity extends BaseEntity implements IUser { 14 | @PrimaryGeneratedColumn() 15 | public id: number; 16 | 17 | @Column({ type: "varchar" }) 18 | public email: string; 19 | 20 | @Exclude() 21 | @Column({ type: "varchar", select: false }) 22 | public password: string; 23 | 24 | @Column({ 25 | type: "enum", 26 | enum: UserRole, 27 | array: true, 28 | }) 29 | public roles: Array; 30 | } 31 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | 4 | import { UserService } from "./user.service"; 5 | import { UserEntity } from "./user.entity"; 6 | import { UserResolver } from "./user.resolver"; 7 | 8 | @Module({ 9 | imports: [TypeOrmModule.forFeature([UserEntity])], 10 | providers: [UserService, UserResolver], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/jwt-gql/src/user/user.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Query, Resolver } from "@nestjs/graphql"; 2 | 3 | import { Roles, User } from "../common/decorators"; 4 | import { UserRole } from "./interfaces"; 5 | import { UserEntity } from "./user.entity"; 6 | import { UserService } from "./user.service"; 7 | import { UserListType, UserType } from "./types"; 8 | 9 | @Resolver() 10 | export class UserResolver { 11 | constructor(private readonly userService: UserService) {} 12 | 13 | @Query(_returns => UserType) 14 | public profile(@User() userEntity: UserEntity): UserEntity { 15 | return userEntity; 16 | } 17 | 18 | @Roles(UserRole.ADMIN) 19 | @Query(_returns => UserListType) 20 | public listUsers(): Promise { 21 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/jwt-gql/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwt-gql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwt-rest/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH 9 | JWT_SECRET_KEY=keyboard_cat 10 | 11 | # AUTH 12 | GOOGLE_CLIENT_ID=XXX 13 | GOOGLE_CLIENT_SECRET=XXX 14 | GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback 15 | 16 | FACEBOOK_CLIENT_ID=XXX 17 | FACEBOOK_CLIENT_SECRET=XXX 18 | FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback 19 | -------------------------------------------------------------------------------- /apps/jwt-rest/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/jwt-rest/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/auth.jwt.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from "@nestjs/common"; 2 | 3 | import { AuthService } from "./auth.service"; 4 | import { UserService } from "../user/user.service"; 5 | import { IJwt } from "../common/jwt"; 6 | import { Public } from "../common/decorators"; 7 | import { JwtLogoutDto, JwtRefreshTokenDto, LoginDto } from "./dto"; 8 | import { UserCreateDto } from "../user/dto"; 9 | 10 | @Public() 11 | @Controller("/auth") 12 | export class AuthJwtController { 13 | constructor( 14 | private readonly authService: AuthService, 15 | private readonly userService: UserService, 16 | ) {} 17 | 18 | @Post("/login") 19 | public login(@Body() dto: LoginDto): Promise { 20 | return this.authService.login(dto); 21 | } 22 | 23 | @Post("/refresh") 24 | async refreshToken(@Body() dto: JwtRefreshTokenDto): Promise { 25 | return this.authService.refresh(dto); 26 | } 27 | 28 | @Get("logout") 29 | public async logout(@Body() dto: JwtLogoutDto): Promise { 30 | await this.authService.delete(dto); 31 | return true; 32 | } 33 | 34 | @Get("signup") 35 | public async signup(@Body() dto: UserCreateDto): Promise { 36 | const userEntity = await this.userService.create(dto); 37 | return this.authService.loginUser(userEntity); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./login"; 2 | export * from "./logout"; 3 | export * from "./refresh"; 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/dto/login.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString, IsEmail, MinLength } from "class-validator"; 4 | 5 | import { ILoginDto } from "../interfaces"; 6 | 7 | export class LoginDto implements ILoginDto { 8 | @ApiProperty() 9 | @IsEmail() 10 | public email: string; 11 | 12 | @ApiProperty() 13 | @IsString() 14 | @MinLength(6) 15 | public password: string; 16 | } 17 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/dto/logout.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { ILogoutDto } from "../interfaces"; 6 | 7 | export class JwtLogoutDto implements ILogoutDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/dto/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { IRefreshDto } from "../interfaces"; 6 | 7 | export class JwtRefreshTokenDto implements IRefreshDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "../../user/interfaces"; 2 | 3 | export interface IAuth { 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | user?: IUser; 7 | userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./login"; 3 | export * from "./logout"; 4 | export * from "./refresh"; 5 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/interfaces/login.ts: -------------------------------------------------------------------------------- 1 | export interface ILoginDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/interfaces/logout.ts: -------------------------------------------------------------------------------- 1 | export interface ILogoutDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/interfaces/refresh.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/strategies/google.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Strategy } from "passport-google-oauth"; 2 | import { Profile } from "passport"; 3 | import { PassportStrategy } from "@nestjs/passport"; 4 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 5 | 6 | import { UserEntity } from "../../user/user.entity"; 7 | import { UserService } from "../../user/user.service"; 8 | 9 | @Injectable() 10 | export class GoogleStrategy extends PassportStrategy(OAuth2Strategy, "google") { 11 | constructor(private readonly userService: UserService) { 12 | super({ 13 | clientID: process.env.GOOGLE_CLIENT_ID!, 14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!, 15 | callbackURL: process.env.GOOGLE_CALLBACK_URL!, 16 | }); 17 | } 18 | 19 | public async validate(_accessToken: string, _refreshToken: string, profile: Profile): Promise { 20 | if (!profile.emails) { 21 | throw new UnauthorizedException(); 22 | } 23 | 24 | const userEntity = await this.userService.findOne({ email: profile.emails[0].value }); 25 | 26 | if (!userEntity) { 27 | throw new UnauthorizedException(); 28 | } 29 | 30 | return userEntity; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./facebook"; 2 | export * from "./google"; 3 | export * from "./jwt"; 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/auth/strategies/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserService } from "../../user/user.service"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy, "jwt") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: process.env.JWT_SECRET_KEY!, 15 | }); 16 | } 17 | 18 | public async validate(payload: { email: string }): Promise { 19 | const userEntity = await this.userService.findOne({ email: payload.email }); 20 | 21 | if (!userEntity) { 22 | throw new UnauthorizedException(); 23 | } 24 | 25 | return userEntity; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "jwt_rest"; 2 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user as IUser; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/guards/facebook.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class FacebookGuard extends AuthGuard("facebook") implements CanActivate {} 6 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/guards/google.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class GoogleGuard extends AuthGuard("google") implements CanActivate {} 6 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./facebook"; 2 | export * from "./google"; 3 | export * from "./jwt"; 4 | export * from "./roles"; 5 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/guards/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class JwtGuard extends AuthGuard("jwt") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | 5 | import { UserRole } from "../../user/interfaces"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class RolesGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.getAllAndOverride>("roles", [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | 18 | if (!roles?.length) { 19 | return true; 20 | } 21 | 22 | const request = context.switchToHttp().getRequest(); 23 | const userEntity = request.user as UserEntity; 24 | 25 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 26 | 27 | if (hasRole) { 28 | return true; 29 | } 30 | 31 | throw new UnauthorizedException("userHasWrongRole"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | import { ConfigService } from "@nestjs/config"; 5 | import passport from "passport"; 6 | 7 | import { AppModule } from "./app.module"; 8 | 9 | async function bootstrap(): Promise { 10 | const app = await NestFactory.create(AppModule); 11 | 12 | const configService = app.get(ConfigService); 13 | 14 | app.use(passport.initialize()); 15 | 16 | const options = new DocumentBuilder() 17 | .setTitle("jwt-rest") 18 | .setDescription("API description") 19 | .setVersion("1.0") 20 | .build(); 21 | const document = SwaggerModule.createDocument(app, options); 22 | SwaggerModule.setup("swagger", app, document); 23 | 24 | const host = configService.get("HOST", "localhost"); 25 | const port = configService.get("PORT", 3000); 26 | 27 | await app.listen(port, host, () => { 28 | console.info(`Express server is running on http://${host}:${port}/`); 29 | }); 30 | } 31 | 32 | void bootstrap(); 33 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "email", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "password", 28 | type: "varchar", 29 | }, 30 | { 31 | name: "roles", 32 | type: `${ns}.user_role_enum`, 33 | isArray: true, 34 | }, 35 | ], 36 | }); 37 | 38 | await queryRunner.createTable(table, true); 39 | } 40 | 41 | public async down(queryRunner: QueryRunner): Promise { 42 | await queryRunner.dropTable(`${ns}.user`); 43 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const passwordHash = "6c9311b5a0c96b76e6535d5c57a96d67a405779d2284aaf154148cdcbefc5af6"; // My5up3r5tr0ngP@55w0rd 8 | 9 | await queryRunner.query(` 10 | INSERT INTO ${ns}.user ( 11 | email, 12 | password, 13 | roles 14 | ) VALUES ( 15 | 'trejgun@gmail.com', 16 | '${passwordHash}', 17 | '{ADMIN}' 18 | ); 19 | `); 20 | } 21 | 22 | public async down(queryRunner: QueryRunner): Promise { 23 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/dto/create.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsEmail, IsString, MinLength } from "class-validator"; 3 | 4 | import { IUserCreateDto } from "../interfaces"; 5 | 6 | export class UserCreateDto implements IUserCreateDto { 7 | @ApiProperty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @ApiProperty() 12 | @IsString() 13 | @MinLength(6) 14 | public password: string; 15 | } 16 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | password: string; 7 | roles: Array; 8 | } 9 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | 3 | import { UserEntity } from "./user.entity"; 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserRole } from "./interfaces"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Controller("/users") 9 | export class UserController { 10 | constructor(private readonly userService: UserService) {} 11 | 12 | @Get("/profile") 13 | @UseInterceptors(ClassSerializerInterceptor) 14 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 15 | return userEntity; 16 | } 17 | 18 | @Get("/") 19 | @Roles(UserRole.ADMIN) 20 | @UseInterceptors(ClassSerializerInterceptor) 21 | public findAll(): Promise<{ rows: Array; count: number }> { 22 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { Exclude } from "class-transformer"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { IUser, UserRole } from "./interfaces"; 6 | 7 | @Entity({ schema: ns, name: "user" }) 8 | export class UserEntity extends BaseEntity implements IUser { 9 | @PrimaryGeneratedColumn() 10 | public id: number; 11 | 12 | @Column({ type: "varchar" }) 13 | public email: string; 14 | 15 | @Exclude() 16 | @Column({ type: "varchar", select: false }) 17 | public password: string; 18 | 19 | @Column({ 20 | type: "enum", 21 | enum: UserRole, 22 | array: true, 23 | }) 24 | public roles: Array; 25 | } 26 | -------------------------------------------------------------------------------- /apps/jwt-rest/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | controllers: [UserController], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/jwt-rest/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /apps/jwt-rest/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwt-rest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/jwt-ws/.env: -------------------------------------------------------------------------------- 1 | # App 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # REDIS 9 | REDIS_WS_URL=redis://localhost:6379/2 10 | 11 | # AUTH 12 | JWT_SECRET_KEY=keyboard_cat 13 | -------------------------------------------------------------------------------- /apps/jwt-ws/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/jwt-ws/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/auth.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseEntity, 3 | BeforeInsert, 4 | BeforeUpdate, 5 | Column, 6 | Entity, 7 | JoinColumn, 8 | OneToOne, 9 | PrimaryGeneratedColumn, 10 | } from "typeorm"; 11 | 12 | import { ns } from "../common/constants"; 13 | import { UserEntity } from "../user/user.entity"; 14 | import { IAuth } from "./interfaces"; 15 | 16 | @Entity({ schema: ns, name: "auth" }) 17 | export class AuthEntity extends BaseEntity implements IAuth { 18 | @PrimaryGeneratedColumn() 19 | public id: number; 20 | 21 | @Column({ type: "varchar" }) 22 | public refreshToken: string; 23 | 24 | @Column({ type: "bigint" }) 25 | public refreshTokenExpiresAt: number; 26 | 27 | @JoinColumn() 28 | @OneToOne(_type => UserEntity) 29 | public user: UserEntity; 30 | 31 | @Column({ type: "int" }) 32 | public userId: number; 33 | 34 | @Column({ type: "timestamptz" }) 35 | public createdAt: string; 36 | 37 | @Column({ type: "timestamptz" }) 38 | public updatedAt: string; 39 | 40 | @BeforeInsert() 41 | public beforeInsert(): void { 42 | const date = new Date(); 43 | this.createdAt = date.toISOString(); 44 | this.updatedAt = date.toISOString(); 45 | } 46 | 47 | @BeforeUpdate() 48 | public beforeUpdate(): void { 49 | const date = new Date(); 50 | this.updatedAt = date.toISOString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/auth.jwt.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from "@nestjs/common"; 2 | 3 | import { AuthService } from "./auth.service"; 4 | import { UserService } from "../user/user.service"; 5 | import { IJwt } from "../common/jwt"; 6 | import { Public } from "../common/decorators"; 7 | import { JwtLogoutDto, JwtRefreshTokenDto, LoginDto } from "./dto"; 8 | import { UserCreateDto } from "../user/dto"; 9 | 10 | @Public() 11 | @Controller("/auth") 12 | export class AuthJwtController { 13 | constructor( 14 | private readonly authService: AuthService, 15 | private readonly userService: UserService, 16 | ) {} 17 | 18 | @Post("/login") 19 | public login(@Body() dto: LoginDto): Promise { 20 | return this.authService.login(dto); 21 | } 22 | 23 | @Post("/refresh") 24 | async refreshToken(@Body() dto: JwtRefreshTokenDto): Promise { 25 | return this.authService.refresh(dto); 26 | } 27 | 28 | @Get("logout") 29 | public async logout(@Body() dto: JwtLogoutDto): Promise { 30 | await this.authService.delete(dto); 31 | return true; 32 | } 33 | 34 | @Get("signup") 35 | public async signup(@Body() dto: UserCreateDto): Promise { 36 | const userEntity = await this.userService.create(dto); 37 | return this.authService.loginUser(userEntity); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { JwtModule } from "@nestjs/jwt"; 4 | import { TypeOrmModule } from "@nestjs/typeorm"; 5 | import { ConfigModule, ConfigService } from "@nestjs/config"; 6 | 7 | import { AuthService } from "./auth.service"; 8 | import { AuthEntity } from "./auth.entity"; 9 | import { UserModule } from "../user/user.module"; 10 | import { AuthJwtController } from "./auth.jwt.controller"; 11 | import { JwtStrategy } from "./strategies"; 12 | import { accessTokenExpiresIn } from "./auth.constants"; 13 | 14 | @Module({ 15 | imports: [ 16 | TypeOrmModule.forFeature([AuthEntity]), 17 | UserModule, 18 | PassportModule, 19 | JwtModule.registerAsync({ 20 | imports: [ConfigModule], 21 | inject: [ConfigService], 22 | useFactory: (configService: ConfigService) => ({ 23 | secret: configService.get("JWT_SECRET_KEY"), 24 | signOptions: { 25 | expiresIn: accessTokenExpiresIn, 26 | }, 27 | }), 28 | }), 29 | ], 30 | controllers: [AuthJwtController], 31 | providers: [AuthService, JwtStrategy], 32 | exports: [AuthService], 33 | }) 34 | export class AuthModule {} 35 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./login"; 2 | export * from "./logout"; 3 | export * from "./refresh"; 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/dto/login.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString, IsEmail, MinLength } from "class-validator"; 4 | 5 | import { ILoginDto } from "../interfaces"; 6 | 7 | export class LoginDto implements ILoginDto { 8 | @ApiProperty() 9 | @IsEmail() 10 | public email: string; 11 | 12 | @ApiProperty() 13 | @IsString() 14 | @MinLength(6) 15 | public password: string; 16 | } 17 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/dto/logout.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { ILogoutDto } from "../interfaces"; 6 | 7 | export class JwtLogoutDto implements ILogoutDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/dto/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { IRefreshDto } from "../interfaces"; 6 | 7 | export class JwtRefreshTokenDto implements IRefreshDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "../../user/interfaces"; 2 | 3 | export interface IAuth { 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | user?: IUser; 7 | userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./login"; 3 | export * from "./logout"; 4 | export * from "./refresh"; 5 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/interfaces/login.ts: -------------------------------------------------------------------------------- 1 | export interface ILoginDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/interfaces/logout.ts: -------------------------------------------------------------------------------- 1 | export interface ILogoutDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/interfaces/refresh.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/auth/strategies/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy, JwtFromRequestFunction } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable } from "@nestjs/common"; 4 | import { WsException } from "@nestjs/websockets"; 5 | import { Request } from "express"; 6 | 7 | import { UserService } from "../../user/user.service"; 8 | import { UserEntity } from "../../user/user.entity"; 9 | 10 | const ExtractJwtFromSocketIoHandshake: JwtFromRequestFunction = (req: Request) => { 11 | // @ts-ignore 12 | return ExtractJwt.fromAuthHeaderWithScheme("bearer")(req.handshake); 13 | }; 14 | 15 | @Injectable() 16 | export class JwtStrategy extends PassportStrategy(Strategy, "jwt") { 17 | constructor(private readonly userService: UserService) { 18 | super({ 19 | jwtFromRequest: ExtractJwtFromSocketIoHandshake, 20 | ignoreExpiration: false, 21 | secretOrKey: process.env.JWT_SECRET_KEY!, 22 | }); 23 | } 24 | 25 | public async validate(payload: { email: string }): Promise { 26 | const userEntity = await this.userService.findOne({ email: payload.email }); 27 | 28 | if (!userEntity) { 29 | throw new WsException("unauthorized"); 30 | } 31 | 32 | return userEntity; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "jwt_ws"; 2 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | export * from "./roles"; 3 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/guards/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | import { WsException } from "@nestjs/websockets"; 5 | 6 | @Injectable() 7 | export class JwtGuard extends AuthGuard("jwt") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public async canActivate(context: ExecutionContext): Promise { 13 | // `super` has to be called to set `user` on `request` 14 | // see https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts 15 | return (super.canActivate(context) as Promise).catch(e => { 16 | const isPublic = this.reflector.getAllAndOverride("isPublic", [ 17 | context.getHandler(), 18 | context.getClass(), 19 | ]); 20 | 21 | if (isPublic) { 22 | return true; 23 | } 24 | 25 | console.error(e); 26 | throw new WsException("unauthorized"); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { WsException } from "@nestjs/websockets"; 4 | import { Socket } from "socket.io"; 5 | 6 | import { UserRole } from "../../user/interfaces"; 7 | import { UserEntity } from "../../user/user.entity"; 8 | 9 | @Injectable() 10 | export class RolesGuard implements CanActivate { 11 | constructor(private readonly reflector: Reflector) {} 12 | 13 | canActivate(context: ExecutionContext): boolean { 14 | const roles = this.reflector.getAllAndOverride>("roles", [ 15 | context.getHandler(), 16 | context.getClass(), 17 | ]); 18 | 19 | if (!roles?.length) { 20 | return true; 21 | } 22 | const socket = context.switchToWs().getClient(); 23 | // @ts-ignore 24 | const userEntity = socket.client.request.user as UserEntity; 25 | 26 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 27 | 28 | if (hasRole) { 29 | return true; 30 | } 31 | 32 | throw new WsException("userHasWrongRole"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/events/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/events/event.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Logger } from "@nestjs/common"; 2 | 3 | import { EventGateway } from "./event.gateway"; 4 | 5 | @Module({ 6 | providers: [Logger, EventGateway], 7 | }) 8 | export class EventModule {} 9 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/events/guards/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | import { WsException } from "@nestjs/websockets"; 5 | 6 | @Injectable() 7 | export class JwtWsGuard extends AuthGuard("jwt") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public async canActivate(context: ExecutionContext): Promise { 13 | // `super` has to be called to set `user` on `request` 14 | // see https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts 15 | return (super.canActivate(context) as Promise).catch(e => { 16 | const isPublic = this.reflector.getAllAndOverride("isPublic", [ 17 | context.getHandler(), 18 | context.getClass(), 19 | ]); 20 | 21 | if (isPublic) { 22 | return true; 23 | } 24 | 25 | console.error(e); 26 | throw new WsException("unauthorized"); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | import { ConfigService } from "@nestjs/config"; 5 | import passport from "passport"; 6 | 7 | import { AppModule } from "./app.module"; 8 | import { RedisIoAdapter } from "./common/adapters/redis-io"; 9 | 10 | async function bootstrap(): Promise { 11 | const app = await NestFactory.create(AppModule); 12 | 13 | const configService = app.get(ConfigService); 14 | 15 | app.useWebSocketAdapter(new RedisIoAdapter(app)); 16 | 17 | app.use(passport.initialize()); 18 | 19 | const options = new DocumentBuilder().setTitle("jwt-ws").setDescription("API description").setVersion("1.0").build(); 20 | const document = SwaggerModule.createDocument(app, options); 21 | SwaggerModule.setup("swagger", app, document); 22 | 23 | const host = configService.get("HOST", "localhost"); 24 | const port = configService.get("PORT", 3000); 25 | 26 | await app.listen(port, host, () => { 27 | console.info(`Express server is running on http://${host}:${port}/`); 28 | }); 29 | } 30 | 31 | void bootstrap(); 32 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async down(queryRunner: QueryRunner): Promise { 7 | await queryRunner.dropSchema(ns); 8 | } 9 | 10 | public async up(queryRunner: QueryRunner): Promise { 11 | await queryRunner.createSchema(ns, true); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "email", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "password", 28 | type: "varchar", 29 | }, 30 | { 31 | name: "roles", 32 | type: `${ns}.user_role_enum`, 33 | isArray: true, 34 | }, 35 | ], 36 | }); 37 | 38 | await queryRunner.createTable(table, true); 39 | } 40 | 41 | public async down(queryRunner: QueryRunner): Promise { 42 | await queryRunner.dropTable(`${ns}.user`); 43 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const passwordHash = "6c9311b5a0c96b76e6535d5c57a96d67a405779d2284aaf154148cdcbefc5af6"; // My5up3r5tr0ngP@55w0rd 8 | 9 | await queryRunner.query(` 10 | INSERT INTO ${ns}.user ( 11 | email, 12 | password, 13 | roles 14 | ) VALUES ( 15 | 'trejgun@gmail.com', 16 | '${passwordHash}', 17 | '{ADMIN}' 18 | ); 19 | `); 20 | } 21 | 22 | public async down(queryRunner: QueryRunner): Promise { 23 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/dto/create.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsEmail, IsString, MinLength } from "class-validator"; 3 | 4 | import { IUserCreateDto } from "../interfaces"; 5 | 6 | export class UserCreateDto implements IUserCreateDto { 7 | @ApiProperty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @ApiProperty() 12 | @IsString() 13 | @MinLength(6) 14 | public password: string; 15 | } 16 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | 3 | import { UserEntity } from "./user.entity"; 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserRole } from "./interfaces"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Controller("/users") 9 | export class UserController { 10 | constructor(private readonly userService: UserService) {} 11 | 12 | @Get("/profile") 13 | @UseInterceptors(ClassSerializerInterceptor) 14 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 15 | return userEntity; 16 | } 17 | 18 | @Get("/") 19 | @Roles(UserRole.ADMIN) 20 | @UseInterceptors(ClassSerializerInterceptor) 21 | public findAll(): Promise<{ rows: Array; count: number }> { 22 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { Exclude } from "class-transformer"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { IUser, UserRole } from "./interfaces"; 6 | 7 | @Entity({ schema: ns, name: "user" }) 8 | export class UserEntity extends BaseEntity implements IUser { 9 | @PrimaryGeneratedColumn() 10 | public id: number; 11 | 12 | @Column({ type: "varchar" }) 13 | public email: string; 14 | 15 | @Exclude() 16 | @Column({ type: "varchar", select: false }) 17 | public password: string; 18 | 19 | @Column({ 20 | type: "enum", 21 | enum: UserRole, 22 | array: true, 23 | }) 24 | public roles: Array; 25 | } 26 | -------------------------------------------------------------------------------- /apps/jwt-ws/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | exports: [UserService], 11 | controllers: [UserController], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/jwt-ws/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrejGun/nestjs-auth/b2cd831662c36a2d93ff2c441b0828a0b306d9fe/apps/jwt-ws/static/favicon.ico -------------------------------------------------------------------------------- /apps/jwt-ws/static/socket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | EthBerry playground 4 | 5 | 6 | 7 | 33 | 34 | 35 |

Open console and press the button

36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /apps/jwt-ws/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/jwt-ws/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/metamask/.env: -------------------------------------------------------------------------------- 1 | # APP 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # AUTH 9 | JWT_SECRET_KEY=keyboard_cat 10 | 11 | # AUTH 12 | GOOGLE_CLIENT_ID=XXX 13 | GOOGLE_CLIENT_SECRET=XXX 14 | GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback 15 | 16 | FACEBOOK_CLIENT_ID=XXX 17 | FACEBOOK_CLIENT_SECRET=XXX 18 | FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback 19 | -------------------------------------------------------------------------------- /apps/metamask/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/metamask/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/auth.constants.ts: -------------------------------------------------------------------------------- 1 | export const accessTokenExpiresIn = 5 * 60 * 1000; // 5 minutes 2 | export const refreshTokenExpiresIn = 30 * 24 * 60 * 60 * 1000; // 30 days 3 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { JwtModule } from "@nestjs/jwt"; 4 | import { TypeOrmModule } from "@nestjs/typeorm"; 5 | import { ConfigModule, ConfigService } from "@nestjs/config"; 6 | 7 | import { AuthService } from "./auth.service"; 8 | import { AuthEntity } from "./auth.entity"; 9 | import { UserModule } from "../user/user.module"; 10 | import { MetamaskStrategy, JwtStrategy } from "./strategies"; 11 | import { accessTokenExpiresIn } from "./auth.constants"; 12 | import { AuthMetamaskController } from "./auth.metamask.controller"; 13 | 14 | @Module({ 15 | imports: [ 16 | TypeOrmModule.forFeature([AuthEntity]), 17 | UserModule, 18 | PassportModule, 19 | ConfigModule, 20 | JwtModule.registerAsync({ 21 | imports: [ConfigModule], 22 | inject: [ConfigService], 23 | useFactory: (configService: ConfigService) => ({ 24 | secret: configService.get("JWT_SECRET_KEY"), 25 | signOptions: { 26 | expiresIn: accessTokenExpiresIn, 27 | }, 28 | }), 29 | }), 30 | ], 31 | controllers: [AuthMetamaskController], 32 | providers: [AuthService, JwtStrategy, MetamaskStrategy], 33 | exports: [AuthService], 34 | }) 35 | export class AuthModule {} 36 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./logout"; 2 | export * from "./refresh"; 3 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/dto/logout.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { ILogoutDto } from "../interfaces"; 6 | 7 | export class JwtLogoutDto implements ILogoutDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/dto/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | 3 | import { IsString } from "class-validator"; 4 | 5 | import { IRefreshDto } from "../interfaces"; 6 | 7 | export class JwtRefreshTokenDto implements IRefreshDto { 8 | @ApiProperty() 9 | @IsString() 10 | public refreshToken: string; 11 | } 12 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/interfaces/auth.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "../../user/interfaces"; 2 | 3 | export interface IAuth { 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | user?: IUser; 7 | userId: number; 8 | } 9 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./logout"; 3 | export * from "./metamask"; 4 | export * from "./refresh"; 5 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/interfaces/logout.ts: -------------------------------------------------------------------------------- 1 | export interface ILogoutDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/interfaces/metamask.ts: -------------------------------------------------------------------------------- 1 | export interface IMetamaskDto { 2 | nonce: string; 3 | signature: string; 4 | wallet: string; 5 | } 6 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/interfaces/refresh.ts: -------------------------------------------------------------------------------- 1 | export interface IRefreshDto { 2 | refreshToken: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | export * from "./metamask"; 3 | -------------------------------------------------------------------------------- /apps/metamask/src/auth/strategies/jwt.ts: -------------------------------------------------------------------------------- 1 | import { ExtractJwt, Strategy } from "passport-jwt"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserService } from "../../user/user.service"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy, "jwt") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: process.env.JWT_SECRET_KEY!, 15 | }); 16 | } 17 | 18 | public async validate(payload: { wallet: string }): Promise { 19 | const userEntity = await this.userService.findOne({ wallet: payload.wallet }); 20 | 21 | if (!userEntity) { 22 | throw new UnauthorizedException(); 23 | } 24 | 25 | return userEntity; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/metamask/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "metamask"; 2 | -------------------------------------------------------------------------------- /apps/metamask/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/metamask/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/metamask/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/metamask/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return request.user as IUser; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/metamask/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./jwt"; 2 | export * from "./metamask"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/metamask/src/common/guards/jwt.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class JwtGuard extends AuthGuard("jwt") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean | Promise | Observable { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | return super.canActivate(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/metamask/src/common/guards/metamask.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | 5 | @Injectable() 6 | export class MetamaskGuard extends AuthGuard("metamask") implements CanActivate { 7 | constructor(private readonly reflector: Reflector) { 8 | super(); 9 | } 10 | 11 | public async canActivate(context: ExecutionContext): Promise { 12 | // `super` has to be called to set `admin` on `request` 13 | // see https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts 14 | return (super.canActivate(context) as Promise).catch(e => { 15 | const isPublic = this.reflector.getAllAndOverride("isPublic", [ 16 | context.getHandler(), 17 | context.getClass(), 18 | ]); 19 | 20 | if (isPublic) { 21 | return true; 22 | } 23 | 24 | console.error(e); 25 | throw new UnauthorizedException("unauthorized"); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/metamask/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { Request } from "express"; 4 | 5 | import { UserRole } from "../../user/interfaces"; 6 | import { UserEntity } from "../../user/user.entity"; 7 | 8 | @Injectable() 9 | export class RolesGuard implements CanActivate { 10 | constructor(private readonly reflector: Reflector) {} 11 | 12 | canActivate(context: ExecutionContext): boolean { 13 | const roles = this.reflector.getAllAndOverride>("roles", [ 14 | context.getHandler(), 15 | context.getClass(), 16 | ]); 17 | 18 | if (!roles?.length) { 19 | return true; 20 | } 21 | 22 | const request = context.switchToHttp().getRequest(); 23 | const userEntity = request.user as UserEntity; 24 | 25 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 26 | 27 | if (hasRole) { 28 | return true; 29 | } 30 | 31 | throw new UnauthorizedException("userHasWrongRole"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /apps/metamask/src/common/jwt.ts: -------------------------------------------------------------------------------- 1 | export interface IJwt { 2 | accessToken: string; 3 | accessTokenExpiresAt: number; 4 | refreshToken: string; 5 | refreshTokenExpiresAt: number; 6 | } 7 | -------------------------------------------------------------------------------- /apps/metamask/src/index.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; 3 | import { NestExpressApplication } from "@nestjs/platform-express"; 4 | import { ConfigService } from "@nestjs/config"; 5 | import passport from "passport"; 6 | 7 | import { AppModule } from "./app.module"; 8 | 9 | async function bootstrap(): Promise { 10 | const app = await NestFactory.create(AppModule); 11 | 12 | const configService = app.get(ConfigService); 13 | 14 | app.use(passport.initialize()); 15 | 16 | const options = new DocumentBuilder() 17 | .addBearerAuth() 18 | .setTitle("metamask") 19 | .setDescription("API description") 20 | .setVersion("1.0") 21 | .build(); 22 | const document = SwaggerModule.createDocument(app, options); 23 | SwaggerModule.setup("swagger", app, document); 24 | 25 | const host = configService.get("HOST", "localhost"); 26 | const port = configService.get("PORT", 3000); 27 | 28 | await app.listen(port, host, () => { 29 | console.info(`Express server is running on http://${host}:${port}/`); 30 | }); 31 | } 32 | 33 | void bootstrap(); 34 | -------------------------------------------------------------------------------- /apps/metamask/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/metamask/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "wallet", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "roles", 28 | type: `${ns}.user_role_enum`, 29 | isArray: true, 30 | }, 31 | ], 32 | }); 33 | 34 | await queryRunner.createTable(table, true); 35 | } 36 | 37 | public async down(queryRunner: QueryRunner): Promise { 38 | await queryRunner.dropTable(`${ns}.user`); 39 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/metamask/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | INSERT INTO ${ns}.user ( 9 | wallet, 10 | roles 11 | ) VALUES ( 12 | '0x87efa7f59bAA8e475F181B36f77A3028494a2cf6', 13 | '{ADMIN}' 14 | ); 15 | `); 16 | } 17 | 18 | public async down(queryRunner: QueryRunner): Promise { 19 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/metamask/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | wallet: string; 3 | } 4 | -------------------------------------------------------------------------------- /apps/metamask/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | export * from "./roles"; 3 | export * from "./user"; 4 | -------------------------------------------------------------------------------- /apps/metamask/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/metamask/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | wallet: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/metamask/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | import { ApiBearerAuth } from "@nestjs/swagger"; 3 | 4 | import { UserEntity } from "./user.entity"; 5 | import { Roles, User } from "../common/decorators"; 6 | import { UserRole } from "./interfaces"; 7 | import { UserService } from "./user.service"; 8 | 9 | @ApiBearerAuth() 10 | @Controller("/users") 11 | export class UserController { 12 | constructor(private readonly userService: UserService) {} 13 | 14 | @Get("/profile") 15 | @UseInterceptors(ClassSerializerInterceptor) 16 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 17 | return userEntity; 18 | } 19 | 20 | @Get("/") 21 | @Roles(UserRole.ADMIN) 22 | @UseInterceptors(ClassSerializerInterceptor) 23 | public findAll(): Promise<{ rows: Array; count: number }> { 24 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/metamask/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | import { IUser, UserRole } from "./interfaces"; 5 | 6 | @Entity({ schema: ns, name: "user" }) 7 | export class UserEntity extends BaseEntity implements IUser { 8 | @PrimaryGeneratedColumn() 9 | public id: number; 10 | 11 | @Column({ type: "varchar" }) 12 | public wallet: string; 13 | 14 | @Column({ 15 | type: "enum", 16 | enum: UserRole, 17 | array: true, 18 | }) 19 | public roles: Array; 20 | } 21 | -------------------------------------------------------------------------------- /apps/metamask/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | controllers: [UserController], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/metamask/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Repository, FindOptionsWhere } from "typeorm"; 2 | 3 | import { Injectable, ConflictException } from "@nestjs/common"; 4 | import { InjectRepository } from "@nestjs/typeorm"; 5 | 6 | import { UserEntity } from "./user.entity"; 7 | import { IUserCreateDto, UserRole } from "./interfaces"; 8 | 9 | @Injectable() 10 | export class UserService { 11 | constructor( 12 | @InjectRepository(UserEntity) 13 | private readonly userEntityRepository: Repository, 14 | ) {} 15 | 16 | public findOne(where: FindOptionsWhere): Promise { 17 | return this.userEntityRepository.findOne({ where }); 18 | } 19 | 20 | public findAndCount(): Promise<[Array, number]> { 21 | return this.userEntityRepository.findAndCount(); 22 | } 23 | 24 | public async create(dto: IUserCreateDto): Promise { 25 | const userEntity = await this.findOne({ wallet: dto.wallet }); 26 | 27 | if (userEntity) { 28 | throw new ConflictException(); 29 | } 30 | 31 | return this.userEntityRepository 32 | .create({ 33 | ...dto, 34 | roles: [UserRole.USER], 35 | }) 36 | .save(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/metamask/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Have to figure our the way to set up all these 4 | 5 | 6 | -------------------------------------------------------------------------------- /apps/metamask/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/metamask/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/session-rest/.env: -------------------------------------------------------------------------------- 1 | # App 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # REDIS 9 | REDIS_SESSION_URL=redis://localhost:6379/1 10 | 11 | # SESSION 12 | SESSION_SECRET_KEY=keyboard_cat 13 | 14 | # AUTH 15 | GOOGLE_CLIENT_ID=XXX 16 | GOOGLE_CLIENT_SECRET=XXX 17 | GOOGLE_CALLBACK_URL=http://localhost:3000/auth/google/callback 18 | 19 | FACEBOOK_CLIENT_ID=XXX 20 | FACEBOOK_CLIENT_SECRET=XXX 21 | FACEBOOK_CALLBACK_URL=http://localhost:3000/auth/facebook/callback 22 | 23 | OIDC_BASE_URI=https://.onelogin.com/oidc/2 24 | OIDC_CLIENT_ID=XXX 25 | OIDC_CLIENT_SECRET=XXX 26 | OIDC_REDIRECT_URI=http://localhost:3000/auth/onelogin/callback 27 | -------------------------------------------------------------------------------- /apps/session-rest/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/session-rest/@types/passport-openidconnect/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "passport-openidconnect"; 2 | -------------------------------------------------------------------------------- /apps/session-rest/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | import { ConfigModule } from "@nestjs/config"; 4 | 5 | import { UserModule } from "../user/user.module"; 6 | import { SessionSerializer } from "./session.serializer"; 7 | import { AuthSessionController } from "./auth.session.controller"; 8 | import { AuthSocialController } from "./auth.social.controller"; 9 | import { FacebookStrategy, GoogleStrategy, LocalStrategy, OneloginStrategy } from "./strategies"; 10 | import { AuthService } from "./auth.service"; 11 | 12 | @Module({ 13 | imports: [UserModule, PassportModule, ConfigModule], 14 | providers: [FacebookStrategy, GoogleStrategy, LocalStrategy, OneloginStrategy, SessionSerializer, AuthService], 15 | controllers: [AuthSessionController, AuthSocialController], 16 | exports: [AuthService], 17 | }) 18 | export class AuthModule {} 19 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { Request, Response } from "express"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { UserService } from "../user/user.service"; 6 | import { UserEntity } from "../user/user.entity"; 7 | import { IUserCreateDto } from "../user/interfaces"; 8 | 9 | @Injectable() 10 | export class AuthService { 11 | constructor(private readonly userService: UserService) {} 12 | 13 | public async signup(data: IUserCreateDto): Promise { 14 | return this.userService.create(data); 15 | } 16 | 17 | public logout(req: Request, res: Response): void { 18 | req.session.destroy(console.error); 19 | req.logout(console.error); 20 | res.clearCookie(ns); 21 | res.status(204); 22 | res.send(""); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/session.serializer.ts: -------------------------------------------------------------------------------- 1 | import { PassportSerializer } from "@nestjs/passport"; 2 | import { Injectable } from "@nestjs/common"; 3 | 4 | import { UserService } from "../user/user.service"; 5 | import { UserEntity } from "../user/user.entity"; 6 | 7 | @Injectable() 8 | export class SessionSerializer extends PassportSerializer { 9 | constructor(private readonly userService: UserService) { 10 | super(); 11 | } 12 | 13 | public serializeUser(user: UserEntity, done: (err: Error | null, user: number) => void): void { 14 | done(null, user.id); 15 | } 16 | 17 | public deserializeUser(id: number, done: (err: Error | null, payload?: UserEntity | null) => void): any { 18 | return this.userService 19 | .findOne({ id }) 20 | .then(userEntity => done(null, userEntity || null)) 21 | .catch(error => done(error)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/strategies/google.ts: -------------------------------------------------------------------------------- 1 | import { OAuth2Strategy } from "passport-google-oauth"; 2 | import { Profile } from "passport"; 3 | import { PassportStrategy } from "@nestjs/passport"; 4 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 5 | 6 | import { UserEntity } from "../../user/user.entity"; 7 | import { UserService } from "../../user/user.service"; 8 | 9 | @Injectable() 10 | export class GoogleStrategy extends PassportStrategy(OAuth2Strategy, "google") { 11 | constructor(private readonly userService: UserService) { 12 | super({ 13 | clientID: process.env.GOOGLE_CLIENT_ID!, 14 | clientSecret: process.env.GOOGLE_CLIENT_SECRET!, 15 | callbackURL: process.env.GOOGLE_CALLBACK_URL!, 16 | }); 17 | } 18 | 19 | public async validate(_accessToken: string, _refreshToken: string, profile: Profile): Promise { 20 | if (!profile.emails) { 21 | throw new UnauthorizedException(); 22 | } 23 | 24 | const userEntity = await this.userService.findOne({ email: profile.emails[0].value }); 25 | 26 | if (!userEntity) { 27 | throw new UnauthorizedException(); 28 | } 29 | 30 | return userEntity; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./facebook"; 2 | export * from "./google"; 3 | export * from "./local"; 4 | export * from "./onelogin"; 5 | -------------------------------------------------------------------------------- /apps/session-rest/src/auth/strategies/local.ts: -------------------------------------------------------------------------------- 1 | import { Strategy } from "passport-local"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserEntity } from "../../user/user.entity"; 6 | import { UserService } from "../../user/user.service"; 7 | 8 | @Injectable() 9 | export class LocalStrategy extends PassportStrategy(Strategy, "local") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | usernameField: "email", 13 | passwordField: "password", 14 | }); 15 | } 16 | 17 | public async validate(email: string, password: string): Promise { 18 | const userEntity = await this.userService.getByCredentials(email, password); 19 | 20 | if (!userEntity) { 21 | throw new UnauthorizedException(); 22 | } 23 | 24 | return userEntity; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "session_rest"; 2 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/facebook.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class FacebookGuard extends AuthGuard("facebook") implements CanActivate { 6 | public async canActivate(context: ExecutionContext): Promise { 7 | const result = (await super.canActivate(context)) as boolean; 8 | const request = context.switchToHttp().getRequest(); 9 | await super.logIn(request); 10 | return result; 11 | } 12 | 13 | public handleRequest(e: Error, userEntity: UserEntity): UserEntity { 14 | if (e) { 15 | throw new UnauthorizedException(e.message); 16 | } 17 | 18 | return userEntity; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/google.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class GoogleGuard extends AuthGuard("google") implements CanActivate { 6 | public async canActivate(context: ExecutionContext): Promise { 7 | const result = (await super.canActivate(context)) as boolean; 8 | const request = context.switchToHttp().getRequest(); 9 | await super.logIn(request); 10 | return result; 11 | } 12 | 13 | public handleRequest(e: Error, userEntity: UserEntity): UserEntity { 14 | if (e) { 15 | throw new UnauthorizedException(e.message); 16 | } 17 | 18 | return userEntity; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./facebook"; 2 | export * from "./google"; 3 | export * from "./local"; 4 | export * from "./login"; 5 | export * from "./onelogin"; 6 | export * from "./roles"; 7 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/local.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | 5 | @Injectable() 6 | export class LocalGuard extends AuthGuard("local") implements CanActivate { 7 | constructor(private readonly reflector: Reflector) { 8 | super(); 9 | } 10 | 11 | public canActivate(context: ExecutionContext): boolean { 12 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 13 | 14 | if (isPublic) { 15 | return true; 16 | } 17 | 18 | const request = context.switchToHttp().getRequest(); 19 | const isAuthenticated: boolean = request.isAuthenticated(); 20 | 21 | if (isAuthenticated) { 22 | return true; 23 | } 24 | 25 | throw new UnauthorizedException(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/login.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class LoginGuard extends AuthGuard("local") { 6 | public async canActivate(context: ExecutionContext): Promise { 7 | const result = (await super.canActivate(context)) as boolean; 8 | const request = context.switchToHttp().getRequest(); 9 | await super.logIn(request); 10 | return result; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/onelogin.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class OneloginGuard extends AuthGuard("onelogin") implements CanActivate {} 6 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | 4 | import { UserRole } from "../../user/interfaces"; 5 | 6 | @Injectable() 7 | export class RolesGuard implements CanActivate { 8 | constructor(private readonly reflector: Reflector) {} 9 | 10 | canActivate(context: ExecutionContext): boolean { 11 | const roles = this.reflector.getAllAndMerge>("roles", [context.getHandler(), context.getClass()]); 12 | 13 | if (!roles.length) { 14 | return true; 15 | } 16 | 17 | const request = context.switchToHttp().getRequest(); 18 | 19 | return request.user.userRoles.some((role: UserRole) => !!roles.find(item => item === role)) as boolean; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/session-rest/src/common/middlewares/session.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import session from "express-session"; 3 | import { RedisStore } from "connect-redis"; 4 | import { Redis } from "ioredis"; 5 | 6 | import { ns } from "../constants"; 7 | 8 | interface ISessionMiddlewareProps { 9 | url: string; 10 | secret: string; 11 | secure?: boolean; 12 | maxAge?: number; 13 | name?: string; 14 | } 15 | 16 | export const sessionMiddleware = (props: ISessionMiddlewareProps): express.RequestHandler => { 17 | const { url, secret, secure = false, name = ns, maxAge = 30 * 24 * 60 * 60 } = props; 18 | return session({ 19 | cookie: { 20 | path: "/", 21 | httpOnly: true, 22 | secure, 23 | maxAge: maxAge * 1000, 24 | signed: false, 25 | sameSite: secure ? "none" : "lax", 26 | }, 27 | name, 28 | resave: false, 29 | secret, 30 | store: new RedisStore({ client: new Redis(url) }), 31 | saveUninitialized: true, 32 | proxy: true, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /apps/session-rest/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/session-rest/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "email", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "password", 28 | type: "varchar", 29 | }, 30 | { 31 | name: "roles", 32 | type: `${ns}.user_role_enum`, 33 | isArray: true, 34 | }, 35 | ], 36 | }); 37 | 38 | await queryRunner.createTable(table, true); 39 | } 40 | 41 | public async down(queryRunner: QueryRunner): Promise { 42 | await queryRunner.dropTable(`${ns}.user`); 43 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/session-rest/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const passwordHash = "6c9311b5a0c96b76e6535d5c57a96d67a405779d2284aaf154148cdcbefc5af6"; // My5up3r5tr0ngP@55w0rd 8 | 9 | await queryRunner.query(` 10 | INSERT INTO ${ns}.user ( 11 | email, 12 | password, 13 | roles 14 | ) VALUES ( 15 | 'trejgun@gmail.com', 16 | '${passwordHash}', 17 | '{ADMIN}' 18 | ); 19 | `); 20 | } 21 | 22 | public async down(queryRunner: QueryRunner): Promise { 23 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/dto/create.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsEmail, IsString, MinLength } from "class-validator"; 3 | 4 | import { IUserCreateDto } from "../interfaces"; 5 | 6 | export class UserCreateDto implements IUserCreateDto { 7 | @ApiProperty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @ApiProperty() 12 | @IsString() 13 | @MinLength(6) 14 | public password: string; 15 | } 16 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | 3 | import { UserEntity } from "./user.entity"; 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserRole } from "./interfaces"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Controller("/users") 9 | export class UserController { 10 | constructor(private readonly userService: UserService) {} 11 | 12 | @Get("/profile") 13 | @UseInterceptors(ClassSerializerInterceptor) 14 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 15 | return userEntity; 16 | } 17 | 18 | @Get("/") 19 | @Roles(UserRole.ADMIN) 20 | @UseInterceptors(ClassSerializerInterceptor) 21 | public findAll(): Promise<{ rows: Array; count: number }> { 22 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { Exclude } from "class-transformer"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { IUser, UserRole } from "./interfaces"; 6 | 7 | @Entity({ schema: ns, name: "user" }) 8 | export class UserEntity extends BaseEntity implements IUser { 9 | @PrimaryGeneratedColumn() 10 | public id: number; 11 | 12 | @Column({ type: "varchar" }) 13 | public email: string; 14 | 15 | @Exclude() 16 | @Column({ type: "varchar", select: false }) 17 | public password: string; 18 | 19 | @Column({ 20 | type: "enum", 21 | enum: UserRole, 22 | array: true, 23 | }) 24 | public roles: Array; 25 | } 26 | -------------------------------------------------------------------------------- /apps/session-rest/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | exports: [UserService], 11 | controllers: [UserController], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/session-rest/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /apps/session-rest/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/session-rest/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/session-ws/.env: -------------------------------------------------------------------------------- 1 | # App 2 | HOST=localhost 3 | PORT=3000 4 | 5 | # DB 6 | POSTGRES_URL=postgres://postgres:password@localhost/postgres 7 | 8 | # REDIS 9 | REDIS_SESSION_URL=redis://localhost:6379/1 10 | REDIS_WS_URL=redis://localhost:6379/2 11 | 12 | # SESSION 13 | SESSION_SECRET_KEY=keyboard_cat 14 | 15 | -------------------------------------------------------------------------------- /apps/session-ws/.npmignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /apps/session-ws/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": "src", 4 | "testRegex": ".spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | }, 8 | "coverageDirectory": "../coverage", 9 | "testEnvironment": "node" 10 | } 11 | -------------------------------------------------------------------------------- /apps/session-ws/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { PassportModule } from "@nestjs/passport"; 3 | 4 | import { UserModule } from "../user/user.module"; 5 | import { SessionSerializer } from "./session.serializer"; 6 | import { AuthController } from "./auth.controller"; 7 | import { LocalStrategy } from "./strategies"; 8 | import { AuthService } from "./auth.service"; 9 | 10 | @Module({ 11 | imports: [UserModule, PassportModule], 12 | providers: [LocalStrategy, SessionSerializer, AuthService], 13 | controllers: [AuthController], 14 | exports: [AuthService], 15 | }) 16 | export class AuthModule {} 17 | -------------------------------------------------------------------------------- /apps/session-ws/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { Request, Response } from "express"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { UserService } from "../user/user.service"; 6 | import { UserEntity } from "../user/user.entity"; 7 | import { IUserCreateDto } from "../user/interfaces"; 8 | 9 | @Injectable() 10 | export class AuthService { 11 | constructor(private readonly userService: UserService) {} 12 | 13 | public async signup(data: IUserCreateDto): Promise { 14 | return this.userService.create(data); 15 | } 16 | 17 | public logout(req: Request, res: Response): void { 18 | req.session.destroy(console.error); 19 | req.logout(console.error); 20 | res.clearCookie(ns); 21 | res.status(204); 22 | res.send(""); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/session-ws/src/auth/session.serializer.ts: -------------------------------------------------------------------------------- 1 | import { PassportSerializer } from "@nestjs/passport"; 2 | import { Injectable } from "@nestjs/common"; 3 | import { UserService } from "../user/user.service"; 4 | import { UserEntity } from "../user/user.entity"; 5 | 6 | @Injectable() 7 | export class SessionSerializer extends PassportSerializer { 8 | constructor(private readonly userService: UserService) { 9 | super(); 10 | } 11 | 12 | public serializeUser(user: UserEntity, done: (err: Error | null, user: number) => void): void { 13 | done(null, user.id); 14 | } 15 | 16 | public deserializeUser(id: number, done: (err: Error | null, payload?: UserEntity | null) => void): any { 17 | return this.userService 18 | .findOne({ id }) 19 | .then(userEntity => done(null, userEntity || null)) 20 | .catch(error => done(error)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /apps/session-ws/src/auth/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./local"; 2 | -------------------------------------------------------------------------------- /apps/session-ws/src/auth/strategies/local.ts: -------------------------------------------------------------------------------- 1 | import { Strategy } from "passport-local"; 2 | import { PassportStrategy } from "@nestjs/passport"; 3 | import { Injectable, UnauthorizedException } from "@nestjs/common"; 4 | 5 | import { UserEntity } from "../../user/user.entity"; 6 | import { UserService } from "../../user/user.service"; 7 | 8 | @Injectable() 9 | export class LocalStrategy extends PassportStrategy(Strategy, "local") { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | usernameField: "email", 13 | passwordField: "password", 14 | }); 15 | } 16 | 17 | public async validate(email: string, password: string): Promise { 18 | const userEntity = await this.userService.getByCredentials(email, password); 19 | 20 | if (!userEntity) { 21 | throw new UnauthorizedException(); 22 | } 23 | 24 | return userEntity; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ns = "session_ws"; 2 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./public"; 2 | export * from "./user"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/decorators/public.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | export const Public = (): ((target: any, key?: any, descriptor?: any) => any) => SetMetadata("isPublic", true); 4 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/decorators/roles.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from "@nestjs/common"; 2 | 3 | import { UserRole } from "../../user/interfaces"; 4 | 5 | export const Roles = (...roles: Array): ((target: any, key?: any, descriptor?: any) => any) => 6 | SetMetadata("roles", [...roles]); 7 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | 3 | import { IUser } from "../../user/interfaces"; 4 | 5 | export const User = createParamDecorator((_data: unknown, ctx: ExecutionContext) => { 6 | const request = ctx.switchToHttp().getRequest(); 7 | return (request.user as IUser) || null; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/guards/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./local"; 2 | export * from "./login"; 3 | export * from "./roles"; 4 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/guards/local.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | import { ExecutionContext, Injectable, CanActivate } from "@nestjs/common"; 3 | import { Reflector } from "@nestjs/core"; 4 | import { AuthGuard } from "@nestjs/passport"; 5 | 6 | @Injectable() 7 | export class LocalGuard extends AuthGuard("local") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | const request = context.switchToHttp().getRequest(); 20 | 21 | return request.isAuthenticated(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/guards/login.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from "@nestjs/common"; 2 | import { AuthGuard } from "@nestjs/passport"; 3 | 4 | @Injectable() 5 | export class LoginGuard extends AuthGuard("local") { 6 | public async canActivate(context: ExecutionContext): Promise { 7 | const result = (await super.canActivate(context)) as boolean; 8 | const request = context.switchToHttp().getRequest(); 9 | await super.logIn(request); 10 | return result; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/guards/roles.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { WsException } from "@nestjs/websockets"; 4 | import { Socket } from "socket.io"; 5 | 6 | import { UserRole } from "../../user/interfaces"; 7 | import { UserEntity } from "../../user/user.entity"; 8 | 9 | @Injectable() 10 | export class RolesGuard implements CanActivate { 11 | constructor(private readonly reflector: Reflector) {} 12 | 13 | canActivate(context: ExecutionContext): boolean { 14 | const roles = this.reflector.getAllAndOverride>("roles", [ 15 | context.getHandler(), 16 | context.getClass(), 17 | ]); 18 | 19 | if (!roles?.length) { 20 | return true; 21 | } 22 | const socket = context.switchToWs().getClient(); 23 | // @ts-ignore 24 | const userEntity = socket.client.request.user as UserEntity; 25 | 26 | const hasRole = userEntity.roles.some((role: UserRole) => !!roles.find(item => item === role)); 27 | 28 | if (hasRole) { 29 | return true; 30 | } 31 | 32 | throw new WsException("userHasWrongRole"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /apps/session-ws/src/common/middlewares/session.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import session from "express-session"; 3 | import { RedisStore } from "connect-redis"; 4 | import { Redis } from "ioredis"; 5 | 6 | import { ns } from "../constants"; 7 | 8 | interface ISessionMiddlewareProps { 9 | url: string; 10 | secret: string; 11 | secure?: boolean; 12 | maxAge?: number; 13 | name?: string; 14 | } 15 | 16 | export const sessionMiddleware = (props: ISessionMiddlewareProps): express.RequestHandler => { 17 | const { url, secret, secure = false, name = ns, maxAge = 30 * 24 * 60 * 60 } = props; 18 | return session({ 19 | cookie: { 20 | path: "/", 21 | httpOnly: true, 22 | secure, 23 | maxAge: maxAge * 1000, 24 | signed: false, 25 | sameSite: secure ? "none" : "lax", 26 | }, 27 | name, 28 | resave: false, 29 | secret, 30 | store: new RedisStore({ client: new Redis(url) }), 31 | saveUninitialized: true, 32 | proxy: true, 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /apps/session-ws/src/events/decorators/session.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | import { Socket } from "socket.io"; 3 | 4 | export const Session = createParamDecorator((_data: unknown, context: ExecutionContext) => { 5 | const socket = context.switchToWs().getClient(); 6 | // @ts-ignore 7 | return socket.client.request.session as Record; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/session-ws/src/events/decorators/user.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from "@nestjs/common"; 2 | import { Socket } from "socket.io"; 3 | 4 | import { IUser } from "../../user/interfaces"; 5 | 6 | export const User = createParamDecorator((_data: unknown, context: ExecutionContext) => { 7 | const socket = context.switchToWs().getClient(); 8 | // @ts-ignore 9 | return (socket.client.request.user as IUser) || null; 10 | }); 11 | -------------------------------------------------------------------------------- /apps/session-ws/src/events/event.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, Logger } from "@nestjs/common"; 2 | 3 | import { EventGateway } from "./event.gateway"; 4 | 5 | @Module({ 6 | providers: [Logger, EventGateway], 7 | }) 8 | export class EventModule {} 9 | -------------------------------------------------------------------------------- /apps/session-ws/src/events/guards/local.ws.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; 2 | import { Reflector } from "@nestjs/core"; 3 | import { AuthGuard } from "@nestjs/passport"; 4 | import { Socket } from "socket.io"; 5 | 6 | @Injectable() 7 | export class WsLocalGuard extends AuthGuard("local") implements CanActivate { 8 | constructor(private readonly reflector: Reflector) { 9 | super(); 10 | } 11 | 12 | public canActivate(context: ExecutionContext): boolean { 13 | const isPublic = this.reflector.getAllAndOverride("isPublic", [context.getHandler(), context.getClass()]); 14 | 15 | if (isPublic) { 16 | return true; 17 | } 18 | 19 | const socket = context.switchToWs().getClient(); 20 | // @ts-ignore 21 | const isAuthenticated: boolean = socket.client.request.isAuthenticated(); 22 | 23 | if (!isAuthenticated) { 24 | throw new UnauthorizedException(); 25 | } 26 | 27 | return isAuthenticated; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/session-ws/src/events/interceptors/session.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "rxjs"; 2 | import { finalize } from "rxjs/operators"; 3 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common"; 4 | import { Socket } from "socket.io"; 5 | 6 | @Injectable() 7 | export class SessionInterceptor implements NestInterceptor { 8 | intercept(context: ExecutionContext, next: CallHandler): Observable { 9 | const socket = context.switchToWs().getClient(); 10 | return next.handle().pipe( 11 | finalize(() => { 12 | // @ts-ignore 13 | socket.client.request.session.save(); 14 | }), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/session-ws/src/migrations/1561991006215-create-schema.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateSchema1561991006215 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.createSchema(ns, true); 8 | } 9 | 10 | public async down(queryRunner: QueryRunner): Promise { 11 | await queryRunner.dropSchema(ns); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/session-ws/src/migrations/1562222612033-create-user-table.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner, Table } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class CreateUserTable1562222612033 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | await queryRunner.query(` 8 | CREATE TYPE ${ns}.user_role_enum AS ENUM ( 9 | 'ADMIN', 10 | 'USER' 11 | ); 12 | `); 13 | 14 | const table = new Table({ 15 | name: `${ns}.user`, 16 | columns: [ 17 | { 18 | name: "id", 19 | type: "serial", 20 | isPrimary: true, 21 | }, 22 | { 23 | name: "email", 24 | type: "varchar", 25 | }, 26 | { 27 | name: "password", 28 | type: "varchar", 29 | }, 30 | { 31 | name: "roles", 32 | type: `${ns}.user_role_enum`, 33 | isArray: true, 34 | }, 35 | ], 36 | }); 37 | 38 | await queryRunner.createTable(table, true); 39 | } 40 | 41 | public async down(queryRunner: QueryRunner): Promise { 42 | await queryRunner.dropTable(`${ns}.user`); 43 | await queryRunner.query(`DROP TYPE ${ns}.user_role_enum;`); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /apps/session-ws/src/migrations/1563804021014-seed-users.ts: -------------------------------------------------------------------------------- 1 | import { MigrationInterface, QueryRunner } from "typeorm"; 2 | 3 | import { ns } from "../common/constants"; 4 | 5 | export class SeedUsers1563804021014 implements MigrationInterface { 6 | public async up(queryRunner: QueryRunner): Promise { 7 | const passwordHash = "6c9311b5a0c96b76e6535d5c57a96d67a405779d2284aaf154148cdcbefc5af6"; // My5up3r5tr0ngP@55w0rd 8 | 9 | await queryRunner.query(` 10 | INSERT INTO ${ns}.user ( 11 | email, 12 | password, 13 | roles 14 | ) VALUES ( 15 | 'trejgun@gmail.com', 16 | '${passwordHash}', 17 | '{ADMIN}' 18 | ); 19 | `); 20 | } 21 | 22 | public async down(queryRunner: QueryRunner): Promise { 23 | await queryRunner.query(`TRUNCATE TABLE ${ns}.user RESTART IDENTITY CASCADE;`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/dto/create.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from "@nestjs/swagger"; 2 | import { IsEmail, IsString, MinLength } from "class-validator"; 3 | 4 | import { IUserCreateDto } from "../interfaces"; 5 | 6 | export class UserCreateDto implements IUserCreateDto { 7 | @ApiProperty() 8 | @IsEmail() 9 | public email: string; 10 | 11 | @ApiProperty() 12 | @IsString() 13 | @MinLength(6) 14 | public password: string; 15 | } 16 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./create"; 2 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/interfaces/create.ts: -------------------------------------------------------------------------------- 1 | export interface IUserCreateDto { 2 | email: string; 3 | password: string; 4 | } 5 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./roles"; 2 | export * from "./user"; 3 | export * from "./create"; 4 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/interfaces/roles.ts: -------------------------------------------------------------------------------- 1 | export enum UserRole { 2 | USER = "USER", 3 | ADMIN = "ADMIN", 4 | } 5 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | import { UserRole } from "./roles"; 2 | 3 | export interface IUser { 4 | id: number; 5 | email: string; 6 | roles: Array; 7 | } 8 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { ClassSerializerInterceptor, Controller, Get, UseInterceptors } from "@nestjs/common"; 2 | 3 | import { UserEntity } from "./user.entity"; 4 | import { Roles, User } from "../common/decorators"; 5 | import { UserRole } from "./interfaces"; 6 | import { UserService } from "./user.service"; 7 | 8 | @Controller("/users") 9 | export class UserController { 10 | constructor(private readonly userService: UserService) {} 11 | 12 | @Get("/profile") 13 | @UseInterceptors(ClassSerializerInterceptor) 14 | public getGloballyProtectedProfile(@User() userEntity: UserEntity): UserEntity { 15 | return userEntity; 16 | } 17 | 18 | @Get("/") 19 | @Roles(UserRole.ADMIN) 20 | @UseInterceptors(ClassSerializerInterceptor) 21 | public findAll(): Promise<{ rows: Array; count: number }> { 22 | return this.userService.findAndCount().then(([rows, count]) => ({ rows, count })); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm"; 2 | import { Exclude } from "class-transformer"; 3 | 4 | import { ns } from "../common/constants"; 5 | import { IUser, UserRole } from "./interfaces"; 6 | 7 | @Entity({ schema: ns, name: "user" }) 8 | export class UserEntity extends BaseEntity implements IUser { 9 | @PrimaryGeneratedColumn() 10 | public id: number; 11 | 12 | @Column({ type: "varchar" }) 13 | public email: string; 14 | 15 | @Exclude() 16 | @Column({ type: "varchar", select: false }) 17 | public password: string; 18 | 19 | @Column({ 20 | type: "enum", 21 | enum: UserRole, 22 | array: true, 23 | }) 24 | public roles: Array; 25 | } 26 | -------------------------------------------------------------------------------- /apps/session-ws/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from "@nestjs/common"; 2 | import { TypeOrmModule } from "@nestjs/typeorm"; 3 | import { UserService } from "./user.service"; 4 | import { UserController } from "./user.controller"; 5 | import { UserEntity } from "./user.entity"; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([UserEntity])], 9 | providers: [UserService], 10 | exports: [UserService], 11 | controllers: [UserController], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /apps/session-ws/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrejGun/nestjs-auth/b2cd831662c36a2d93ff2c441b0828a0b306d9fe/apps/session-ws/static/favicon.ico -------------------------------------------------------------------------------- /apps/session-ws/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | EthBerry playground 4 | 5 | 6 | 29 | 30 | 31 |
32 | 33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /apps/session-ws/static/socket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | EthBerry playground 4 | 5 | 6 | 7 | 26 | 27 | 28 |

Open console and press the button

29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /apps/session-ws/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/session-ws/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": false, 15 | "outDir": "./dist", 16 | "resolveJsonModule": true, 17 | "rootDir": "./src", 18 | "skipLibCheck": true, 19 | "sourceMap": true, 20 | "strictBindCallApply": true, 21 | "strictFunctionTypes": true, 22 | "strictNullChecks": true, 23 | "strictPropertyInitialization": false, 24 | "target": "es2017" 25 | }, 26 | "exclude": ["node_modules", "dist"] 27 | } 28 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | postgres: 5 | container_name: postgres 6 | image: postgres 7 | user: root 8 | ports: 9 | - "5432:5432" 10 | environment: 11 | POSTGRES_USER: "${POSTGRES_USER}" 12 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" 13 | POSTGRES_DB: "${POSTGRES_DB}" 14 | volumes: 15 | - ${BASE_PATH}/postgres:/var/lib/postgresql/data 16 | - ${BASE_PATH}/pgbckp:/pgbckp 17 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptRules from "@ethberry/eslint-config/presets/tsx.mjs"; 2 | import jestRules from "@ethberry/eslint-config/tests/jest.mjs"; 3 | 4 | export default [ 5 | { 6 | ignores: ["**/dist"], 7 | }, 8 | 9 | { 10 | languageOptions: { 11 | parserOptions: { 12 | project: ["./tsconfig.eslint.json", "./*/tsconfig.eslint.json"], 13 | tsconfigRootDir: import.meta.dirname, 14 | }, 15 | }, 16 | }, 17 | 18 | ...typescriptRules, 19 | ...jestRules, 20 | ]; 21 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["apps/*"], 3 | "version": "independent" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "trejgun+opensource@gmail.com", 4 | "name": "TrejGun", 5 | "url": "https://ethberry.io/" 6 | }, 7 | "dependencies": { 8 | "@lerna/legacy-package-management": "8.2.2", 9 | "lerna": "8.2.2" 10 | }, 11 | "devDependencies": { 12 | "@ethberry/eslint-config": "5.0.5", 13 | "husky": "9.1.7", 14 | "lint-staged": "15.5.2", 15 | "prettier": "3.5.3" 16 | }, 17 | "engines": { 18 | "node": ">=22" 19 | }, 20 | "license": "MIT", 21 | "name": "@trejgun/nestjs-auth", 22 | "private": true, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/trejgun/nestjs-auth" 26 | }, 27 | "scripts": { 28 | "bootstrap": "lerna bootstrap --hoist", 29 | "build": "lerna run build --stream", 30 | "clean": "sh scripts/clean.sh", 31 | "lint": "lerna run lint --concurrency 1", 32 | "prepare": "husky", 33 | "prettier": "prettier . --write", 34 | "test": "sh scripts/test.sh" 35 | }, 36 | "version": "0.0.1" 37 | } 38 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>trejgun/renovate-config"], 3 | "baseBranches": ["master"] 4 | } 5 | -------------------------------------------------------------------------------- /scripts/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo -e "\033[34mCleaning...\n\033[0m"; 4 | 5 | set -e # this will cause the shell to exit immediately if any command exits with a nonzero exit value. 6 | 7 | find . -type d -name "dist" | xargs rm -rf 8 | find . -type d -name "node_modules" | xargs rm -rf 9 | find . -type f -name "package-lock.json" | xargs rm -rf 10 | find . -type f -name "yarn.lock" | xargs rm -rf 11 | 12 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | echo -e "\033[34mTesting...\n\033[0m"; 5 | 6 | set -e # this will cause the shell to exit immediately if any command exits with a nonzero exit value. 7 | 8 | export NODE_ENV=$NODE_ENV 9 | export POSTGRES_URL=$POSTGRES_URL 10 | 11 | lerna bootstrap --hoist --ignore-scripts 12 | lerna run build --stream 13 | lerna run test --concurrency 1 14 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "lib": ["ES2021"], 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noImplicitAny": true, 12 | "noImplicitReturns": true, 13 | "noImplicitThis": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "outDir": "./dist", 17 | "resolveJsonModule": true, 18 | "rootDir": "./src", 19 | "skipLibCheck": true, 20 | "sourceMap": true, 21 | "strictBindCallApply": true, 22 | "strictFunctionTypes": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": false, 25 | "target": "ES2021" 26 | }, 27 | "exclude": ["node_modules", "dist"] 28 | } 29 | --------------------------------------------------------------------------------