├── .gitattributes
├── .github
├── pr-labeler.yml
└── workflows
│ ├── cron-dependency-checker-workflow.yml
│ ├── issue-assigned-workflows.yml
│ ├── issue-open-workflows.yml
│ ├── on-push-tags.yml
│ ├── on-version-update.yml
│ ├── pr-labeled-at-develop-workflows.yml
│ ├── pr-open-to-demos.yml
│ ├── pr-open-to-master-workflows.yml
│ └── pr-open-workflows.yml
├── .gitignore
├── .run
├── env.run.xml
├── install.run.xml
├── outdated.run.xml
├── postmanDevelop.run.xml
├── postmanLocal.run.xml
├── start.run.xml
├── startLocal.run.xml
└── update.run.xml
├── LICENSE
├── README.md
├── app.json
├── app
├── consumer
│ └── auth-queue.consumer.ts
├── controller
│ ├── auth.controller.ts
│ ├── info.controller.ts
│ ├── social-login.controller.ts
│ └── two-factor.controller.ts
├── enum
│ ├── social-login.enum.ts
│ └── user-role.enum.ts
├── interface
│ ├── auth-token.interface.ts
│ └── two-factor-code.interface.ts
├── repository
│ ├── social-login.repository.ts
│ ├── token.repository.ts
│ ├── two-factor.repository.ts
│ └── user.repository.ts
└── route
│ ├── auth.route.ts
│ ├── index.route.ts
│ ├── info.route.ts
│ ├── monitor.route.ts
│ ├── social-login.route.ts
│ └── two-factor-code.route.ts
├── assets
├── requests
│ ├── auth-requests.rest
│ ├── info-requests.rest
│ ├── monitor-requests.rest
│ ├── social-login-requests.rest
│ ├── team-requests.rest
│ └── two-factor-requests.rest
├── sql
│ └── preload.tables.psql
├── test-results
│ ├── postman-dark.html
│ └── postman.html
└── tests
│ ├── regression-tests
│ └── postman
│ │ ├── README.md
│ │ ├── auth-server-regression.postman_collection.json
│ │ ├── auth-server-regression.postman_environment_develop.json
│ │ └── auth-server-regression.postman_environment_local.json
│ └── stress-tests
│ └── login.jmx
├── auth-server.main.ts
├── dependency-checker.ts
├── docs
├── OUTDATED.md
├── REGRESSION_TESTS.md
└── SOCIAL_LOGIN.md
├── env.sh
├── environment.ts
├── package.json
├── tsconfig.json
├── version-generator.ts
└── version.ts
/.gitattributes:
--------------------------------------------------------------------------------
1 | assets/* linguist-vendored
--------------------------------------------------------------------------------
/.github/pr-labeler.yml:
--------------------------------------------------------------------------------
1 | bug: 'bug/*'
2 | dependency: 'dependency/*'
3 | documentation: 'documentation/*'
4 | feature: 'feature/*'
5 | hotfix: 'hotfix/*'
6 | quality: 'quality/*'
7 | workflow: 'workflow/*'
8 |
--------------------------------------------------------------------------------
/.github/workflows/cron-dependency-checker-workflow.yml:
--------------------------------------------------------------------------------
1 | name: Cron Dependency Checker Workflow
2 |
3 | on:
4 | schedule:
5 | - cron: '0 4 * * 1'
6 |
7 | jobs:
8 | cron-dependency-checker:
9 | name: 'Cron Dependency Checker'
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout Repo
13 | uses: actions/checkout@v2
14 | with:
15 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
16 |
17 | - name: Install Node
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: 16
21 |
22 | - name: NPM Install
23 | run: npm i
24 | - name: Npm Outdated
25 | run: npm run outdated
26 |
27 | - name: Check for Changes
28 | run: |
29 | if git diff --exit-code; then
30 | echo "changes_exist=false" >> $GITHUB_ENV
31 | else
32 | echo "changes_exist=true" >> $GITHUB_ENV
33 | fi
34 |
35 | - name: Git Commit and Push
36 | if: ${{ env.changes_exist == 'true' }}
37 | run: |
38 | git config --global user.email "98660390+oth-service-user@users.noreply.github.com"
39 | git config --global user.name "OTH Service User"
40 | git commit -am "Workflow/dependency check"
41 | git push
42 |
--------------------------------------------------------------------------------
/.github/workflows/issue-assigned-workflows.yml:
--------------------------------------------------------------------------------
1 | name: Issue Assigned Workflows
2 |
3 | on:
4 | issues:
5 | types: [ assigned ]
6 |
7 | jobs:
8 | automate-project-columns:
9 | name: 'Automate Project Columns'
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Move Issue to In-Progress
14 | uses: alex-page/github-project-automation-plus@v0.3.0
15 | with:
16 | project: Open Template Hub Servers
17 | column: In progress
18 | repo-token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
19 |
--------------------------------------------------------------------------------
/.github/workflows/issue-open-workflows.yml:
--------------------------------------------------------------------------------
1 | name: Issue Open Workflows
2 |
3 | on:
4 | issues:
5 | types: [ opened ]
6 |
7 | jobs:
8 | automate-project-columns:
9 | name: "Automate Project Columns"
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Move Issue to In-Progress
14 | uses: alex-page/github-project-automation-plus@v0.3.0
15 | with:
16 | project: Open Template Hub Servers
17 | column: To do
18 | repo-token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
19 |
20 | milestone-binder:
21 | name: "Milestone Binder"
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | - name: Set Milestone of the Issue
26 | uses: Code-Hex/auto-milestone-binder@v1.0.1
27 | with:
28 | github-token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.github/workflows/on-push-tags.yml:
--------------------------------------------------------------------------------
1 | name: On Push Tags Workflows
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | tagged-release:
10 | name: 'Tagged Release'
11 | runs-on: 'ubuntu-latest'
12 |
13 | steps:
14 | - name: Generate Release From Tag
15 | uses: 'marvinpinto/action-automatic-releases@latest'
16 | with:
17 | repo_token: '${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}'
18 | prerelease: false
19 |
--------------------------------------------------------------------------------
/.github/workflows/on-version-update.yml:
--------------------------------------------------------------------------------
1 | name: On Version Update Workflows
2 |
3 | on:
4 | push:
5 | paths-ignore:
6 | - 'package.json'
7 | branches:
8 | - 'master'
9 |
10 | jobs:
11 | bump-version:
12 | name: 'Update Version'
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - name: Checkout Repo
17 | uses: actions/checkout@v2
18 | with:
19 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
20 |
21 | - name: Install Node
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: 16
25 |
26 | - name: Bump Version and Create Tag
27 | uses: phips28/gh-action-bump-version@master
28 | with:
29 | tag-prefix: ''
30 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
31 |
32 | create-version-update-pr:
33 | name: 'Create Version Update PR'
34 | runs-on: ubuntu-latest
35 | needs: bump-version
36 |
37 | steps:
38 | - name: Checkout Repo
39 | uses: actions/checkout@v2
40 | with:
41 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
42 | ref: develop
43 |
44 | - name: Hard Reset Develop from Master
45 | run: |
46 | git fetch origin master:master
47 | git reset --hard master
48 |
49 | - name: Create PR
50 | uses: peter-evans/create-pull-request@v3
51 | with:
52 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
53 | branch: workflow/version-update
54 | delete-branch: true
55 | base: develop
56 | title: 'Workflow/version update'
57 |
--------------------------------------------------------------------------------
/.github/workflows/pr-labeled-at-develop-workflows.yml:
--------------------------------------------------------------------------------
1 | name: PR Labeled at Develop Workflows
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - labeled
7 | branches:
8 | - develop
9 |
10 | jobs:
11 | auto-merge:
12 | name: 'Auto Merge'
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: automerge
16 | uses: 'pascalgn/automerge-action@v0.14.3'
17 | env:
18 | GITHUB_TOKEN: '${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}'
19 | MERGE_LABELS: 'workflow'
20 | MERGE_COMMIT_MESSAGE: 'Auto merge for PR with workflow label'
21 | MERGE_FORKS: 'false'
22 | MERGE_RETRY_SLEEP: '60000'
23 |
--------------------------------------------------------------------------------
/.github/workflows/pr-open-to-demos.yml:
--------------------------------------------------------------------------------
1 | name: PR Open To Demos Workflows
2 |
3 | on:
4 | pull_request:
5 | types: [ opened ]
6 | branches:
7 | - demo/*
8 |
9 | jobs:
10 | reset-demo-from-develop:
11 | name: 'Reset Demo From Develop'
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Checkout Repo
15 | uses: actions/checkout@v2
16 | with:
17 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
18 | ref: ${{ github.event.pull_request.base.ref }}
19 | - name: Hard Reset Demo From Develop
20 | run: |
21 | git fetch origin develop:develop
22 | git reset --hard origin/develop
23 | git push -f
24 |
--------------------------------------------------------------------------------
/.github/workflows/pr-open-to-master-workflows.yml:
--------------------------------------------------------------------------------
1 | name: PR Open to Master Workflows
2 |
3 | on:
4 | pull_request:
5 | types: [ opened ]
6 | branches:
7 | - master
8 |
9 | jobs:
10 | postman-test-run:
11 | name: "Postman Regression Test"
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: Checkout Repo
16 | uses: actions/checkout@v2
17 | with:
18 | token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
19 | ref: develop
20 |
21 | - name: Install Node
22 | uses: actions/setup-node@v1
23 | with:
24 | node-version: 16
25 |
26 | - name: Install Newman
27 | run: |
28 | npm install -g newman
29 | npm install -g newman-reporter-htmlextra
30 |
31 | - name: Run Regression Tests
32 | run: |
33 | npm install
34 | npm run-script postmanDevelop --adminAuthToken=admin-auth-token=${{ secrets.ADMIN_AUTH_TOKEN }} --responseEncryptionSecret=response-encryption-secret=${{ secrets.RESPONSE_ENCRYPTION_SECRET }}
35 |
36 | - name: Upload Test Results
37 | uses: actions/upload-artifact@v2
38 | with:
39 | name: PostmanReports
40 | path: ./assets/test-results
41 |
--------------------------------------------------------------------------------
/.github/workflows/pr-open-workflows.yml:
--------------------------------------------------------------------------------
1 | name: PR Open Workflows
2 |
3 | on:
4 | pull_request:
5 | types: [ opened ]
6 | branches-ignore: [ 'workflow/dependency-update' ]
7 |
8 | jobs:
9 | pr-labeler:
10 | name: 'Add Label to PR'
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Add Label to PR
15 | uses: TimonVS/pr-labeler-action@v3
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
18 |
19 | automate-project-columns:
20 | name: 'Automate Project Columns'
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - name: Move PR to In-Progress
25 | uses: alex-page/github-project-automation-plus@v0.3.0
26 | with:
27 | project: Open Template Hub Servers
28 | column: In progress
29 | repo-token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
30 |
31 | milestone-binder:
32 | name: 'Milestone Binder'
33 | runs-on: ubuntu-latest
34 |
35 | steps:
36 | - name: Set Milestone of the PR
37 | uses: Code-Hex/auto-milestone-binder@v1.0.1
38 | with:
39 | github-token: ${{ secrets.MASTER_BRANCH_ACCESS_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Out folder
29 | out/
30 |
31 | # Vscode
32 | .vscode/
33 |
34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
35 | .grunt
36 |
37 | # Bower dependency directory (https://bower.io/)
38 | bower_components
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Microbundle cache
63 | .rpt2_cache/
64 | .rts2_cache_cjs/
65 | .rts2_cache_es/
66 | .rts2_cache_umd/
67 |
68 | # Optional REPL history
69 | .node_repl_history
70 |
71 | # Output of 'npm pack'
72 | *.tgz
73 |
74 | # Yarn Integrity file
75 | .yarn-integrity
76 |
77 | # dotenv environment variables file
78 | .env
79 | .env.test
80 |
81 | # parcel-bundler cache (https://parceljs.org/)
82 | .cache
83 |
84 | # Next.js build output
85 | .next
86 |
87 | # Nuxt.js build / generate output
88 | .nuxt
89 | dist
90 |
91 | # Gatsby files
92 | .cache/
93 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
94 | # https://nextjs.org/blog/next-9-1#public-directory-support
95 | # public
96 |
97 | # vuepress build output
98 | .vuepress/dist
99 |
100 | # Serverless directories
101 | .serverless/
102 |
103 | # FuseBox cache
104 | .fusebox/
105 |
106 | # DynamoDB Local files
107 | .dynamodb/
108 |
109 | # TernJS port file
110 | .tern-port
111 |
112 | package-lock.json
113 |
114 | .idea/*
115 | icon.png
116 |
--------------------------------------------------------------------------------
/.run/env.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.run/install.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.run/outdated.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.run/postmanDevelop.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.run/postmanLocal.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.run/start.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.run/startLocal.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.run/update.run.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Furkan Yavuz
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Open Template Hub - Auth Server Template v5
9 |
10 |
11 | [](LICENSE)
12 | [](https://github.com/open-template-hub/auth-server-template/issues)
13 | [](https://github.com/open-template-hub/auth-server-template/pulls?q=is%3Apr+is%3Aclosed)
14 | [](https://github.com/open-template-hub/auth-server-template/commits/master)
15 | [](https://github.com/open-template-hub/auth-server-template/releases)
16 | [](https://sonarcloud.io/dashboard?id=open-template-hub_auth-server-template)
17 | [](https://github.com/open-template-hub/auth-server-template/blob/develop/assets/test-results/postman.html)
18 |
19 | Auth Server Template is a generic open-source authentication server that has a simple yet powerful design to connect your business with all OAuth 2.0 and OAuth supporting third-party companies (like Google, Facebook, Twitter, or LinkedIn). It also supports a basic username-password authentication system.
20 |
21 | ## Ways to Begin
22 |
23 | ### 1. Express Deploy
24 |
25 | Deploy this template to Heroku
26 |
27 | [](https://heroku.com/deploy?template=https://github.com/open-template-hub/auth-server-template)
28 |
29 | ### 2. Start with Server Generator
30 |
31 | Create your server with Server Generator Package
32 |
33 | [](https://www.npmjs.com/package/@open-template-hub/server-generator)
34 |
35 | ### 3. GitHub Template
36 |
37 | Use this repository as a Template
38 |
39 | [](https://github.com/open-template-hub/auth-server-template/generate)
40 |
41 | ## Installations
42 |
43 | Install **nodejs** and **npm** via **[nodejs.org](https://nodejs.org)**.
44 |
45 | Check installed versions of **nodejs** and **npm** via running following commands:
46 |
47 | ```
48 | node -v
49 | npm -v
50 | ```
51 |
52 | Check project's current **nodejs** and **npm** version from **[package.json](package.json)**.
53 |
54 | ## Environment Variables
55 |
56 | If you don't give **RESPONSE_ENCRYPTION_SECRET**, response encryption mechanism will be disabled automatically.
57 |
58 | ```applescript
59 | PORT=4001
60 |
61 | PROJECT=OTH
62 | MODULE=AuthServer
63 | ENVIRONMENT=Local
64 |
65 | CLIENT_URL=http://localhost:4200
66 | ADMIN_CLIENT_URLS="http://localhost:4202"
67 |
68 | DATABASE_URL={Database Connection Url}
69 | POSTGRESQL_CONNECTION_LIMIT={Postgresql Connection Limit}
70 |
71 | MONGODB_URI={Database Connection Url}
72 | MONGODB_CONNECTION_LIMIT={MongoDB Connection Limit}
73 |
74 | CLOUDAMQP_APIKEY={MQ Api Key}
75 | CLOUDAMQP_URL={MQ Connection Url}
76 |
77 | AUTH_SERVER_QUEUE_CHANNEL=oth_auth_queue
78 | ORCHESTRATION_SERVER_QUEUE_CHANNEL=oth_orchestration_queue
79 |
80 | REDISCLOUD_URL={Redis Connection Url}
81 | REDIS_CONNECTION_LIMIT={Redis Connection Limit}
82 |
83 | AUTO_VERIFY=false
84 |
85 | ACCESS_TOKEN_EXPIRE=1hour
86 | ACCESS_TOKEN_SECRET={Access Token Secret}
87 |
88 | REFRESH_TOKEN_EXPIRE=30days
89 | REFRESH_TOKEN_SECRET={Refresh Token Secret}
90 |
91 | JOIN_TEAM_TOKEN_EXPIRE=10days
92 | JOIN_TEAM_TOKEN_SECRET={Join Team Token Secret}
93 |
94 | RESET_PASSWORD_TOKEN_EXPIRE=1day
95 | RESET_PASSWORD_TOKEN_SECRET={Reset Token Secret}
96 |
97 | VERIFICATION_TOKEN_SECRET={Verification Token Secret
98 |
99 | RESPONSE_ENCRYPTION_SECRET={Response Encryption Secret}
100 |
101 | PREAUTH_TOKEN_SECRET={Pre Auth Token Secret}
102 |
103 | TWO_FACTOR_EXPIRE=90
104 | TWO_FACTOR_CODE_LENGTH=5
105 | TWO_FACTOR_CODE_TYPE=numeric
106 | ```
107 |
108 | ## Social Login Configurations
109 |
110 | To be able to use social login mechanism, refer to **[SOCIAL_LOGIN.md](docs/SOCIAL_LOGIN.md)** file.
111 |
112 |
113 |
114 | ## Postman Regression Tests
115 |
116 | To be able to configure regression tests, refer to **[REGRESSION_TESTS.md](docs/REGRESSION_TESTS.md)** file.
117 |
118 |
119 |
120 | ## Http Requests
121 |
122 | You can find list of available http request in the [requests](assets/requests) directory. You can run http requests directly via **WebStorm**, for more information check out: [jetbrains.com/help/idea/http-client-in-product-code-editor.html](https://jetbrains.com/help/idea/http-client-in-product-code-editor.html)
123 |
124 | ## Contributors
125 |
126 |
127 |
128 |
129 |
136 |
137 |
138 |
139 |
140 |
141 | ## Contributing
142 |
143 | Refer to **[CONTRIBUTING.md](https://github.com/open-template-hub/.github/blob/master/docs/CONTRIBUTING.md)** to see how to contribute to Open Template Hub.
144 |
145 |
146 |
147 | ## Code of Conduct
148 |
149 | Refer to **[CODE_OF_CONDUCT.md](https://github.com/open-template-hub/.github/blob/master/docs/CODE_OF_CONDUCT.md)** to see contributor covenant code of conduct.
150 |
151 |
152 |
153 | ## LICENSE
154 |
155 | The source code for this project is released under the [MIT License](LICENSE).
156 |
157 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-server-template",
3 | "description": "Auth Server Template is a generic open-source authentication server that has a simple yet powerful design to connect your business with all OAuth 2.0 and OAuth supporting third-party companies (like Google, Facebook, Twitter, or LinkedIn). It also supports a basic username-password authentication system.",
4 | "website": "https://opentemplatehub.com/product/server/auth-server-template",
5 | "repository": "https://github.com/open-template-hub/auth-server-template",
6 | "logo": "https://raw.githubusercontent.com/open-template-hub/open-template-hub.github.io/master/assets/logo/server/auth-server-logo.png",
7 | "keywords": [
8 | "nodejs",
9 | "template",
10 | "oauth",
11 | "oauth2 express",
12 | "authentication",
13 | "server facebook-login",
14 | "twitter-login google-login",
15 | "social-login linkedin-login",
16 | "nodejs-express github-login",
17 | "twitch-login",
18 | "backend",
19 | "rest",
20 | "node",
21 | "nodejs",
22 | "typescript",
23 | "template",
24 | "server template",
25 | "open template hub"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/app/consumer/auth-queue.consumer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AbstractQueueConsumer,
3 | AuthActionType,
4 | ContextArgs,
5 | MessageQueueChannelType,
6 | QueueConsumer,
7 | } from '@open-template-hub/common';
8 |
9 | export class AuthQueueConsumer
10 | extends AbstractQueueConsumer
11 | implements QueueConsumer
12 | {
13 | constructor() {
14 | super();
15 | this.ownerChannelType = MessageQueueChannelType.AUTH;
16 | }
17 |
18 | init = (channel: string, ctxArgs: ContextArgs) => {
19 | this.channel = channel;
20 | this.ctxArgs = ctxArgs;
21 | return this;
22 | };
23 |
24 | onMessage = async (msg: any) => {
25 | if (msg !== null) {
26 | const msgStr = msg.content.toString();
27 | const msgObj = JSON.parse(msgStr);
28 |
29 | const message: AuthActionType = msgObj.message;
30 |
31 | // Decide requeue in the error handling
32 | let requeue = false;
33 |
34 | if (message.example) {
35 | const exampleHook = async () => {
36 | console.log('Auth server example');
37 | };
38 |
39 | await this.operate(msg, msgObj, requeue, exampleHook);
40 | } else {
41 | console.log('Message will be rejected: ', msgObj);
42 | this.channel.reject(msg, false);
43 | }
44 | }
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/app/controller/auth.controller.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds auth controller
3 | */
4 |
5 | import {
6 | AccountVerificationMailActionParams,
7 | ForgetPasswordMailActionParams,
8 | HttpError,
9 | MailActionType,
10 | MessageQueueChannelType,
11 | MessageQueueProvider,
12 | MongoDbProvider,
13 | PostgreSqlProvider,
14 | QueryFilters,
15 | QueueMessage,
16 | ResponseCode,
17 | TokenUtil,
18 | User,
19 | UserRole
20 | } from '@open-template-hub/common';
21 | import bcrypt from 'bcrypt';
22 | import { Environment } from '../../environment';
23 | import { TwoFactorCode } from '../interface/two-factor-code.interface';
24 | import { TokenRepository } from '../repository/token.repository';
25 | import { UserRepository } from '../repository/user.repository';
26 | import { TwoFactorCodeController } from './two-factor.controller';
27 |
28 | export class AuthController {
29 |
30 | environment: Environment;
31 | tokenUtil: TokenUtil;
32 |
33 | constructor() {
34 | this.environment = new Environment();
35 | this.tokenUtil = new TokenUtil( this.environment.args() );
36 | }
37 |
38 | /**
39 | * sign up user
40 | * @param db database
41 | * @param message_queue_provider message queue
42 | * @param user user
43 | * @param languageCode
44 | */
45 | signup = async (
46 | db: PostgreSqlProvider,
47 | mongodb_provider: MongoDbProvider,
48 | message_queue_provider: MessageQueueProvider,
49 | origin: string,
50 | user: User,
51 | languageCode?: string
52 | ) => {
53 | if ( !user.password || !user.username || !user.email ) {
54 | let e = new Error( 'username, password and email required' ) as HttpError;
55 | e.responseCode = ResponseCode.BAD_REQUEST;
56 | console.error( e );
57 | throw e;
58 | }
59 |
60 | const hashedPassword = await bcrypt.hash( user.password, 10 );
61 | const userRepository = new UserRepository( db );
62 |
63 | await userRepository.insertUser( {
64 | username: user.username,
65 | password: hashedPassword,
66 | role: UserRole.DEFAULT,
67 | email: user.email,
68 | } as User );
69 |
70 | const tokenUtil = new TokenUtil( this.environment.args() );
71 | const verificationToken = tokenUtil.generateVerificationToken( user );
72 |
73 | const isAutoVerify = process.env.AUTO_VERIFY === 'true';
74 |
75 | if ( isAutoVerify ) {
76 | await this.verify( db, verificationToken );
77 | return this.login( db, mongodb_provider, message_queue_provider, origin, user );
78 | } else {
79 | const orchestrationChannelTag =
80 | this.environment.args().mqArgs?.orchestrationServerMessageQueueChannel;
81 | const verificationParams = {
82 | user: user.username,
83 | email: user.email,
84 | accountVerificationToken: verificationToken,
85 | clientVerificationSuccessUrl: origin + '/verify-account'
86 | } as AccountVerificationMailActionParams;
87 | const message = {
88 | sender: MessageQueueChannelType.AUTH,
89 | receiver: MessageQueueChannelType.MAIL,
90 | message: {
91 | mailType: {
92 | verifyAccount: {
93 | params: verificationParams,
94 | },
95 | },
96 | language: languageCode,
97 | } as MailActionType,
98 | } as QueueMessage;
99 | await message_queue_provider.publish(
100 | message,
101 | orchestrationChannelTag as string
102 | );
103 | return { email: user.email };
104 | }
105 | };
106 |
107 | /**
108 | * login user
109 | * @param db database
110 | * @param user user
111 | */
112 | login = async (
113 | db: PostgreSqlProvider,
114 | mongoDbProvider: MongoDbProvider,
115 | messageQueueProvider: MessageQueueProvider,
116 | origin: string,
117 | user: User,
118 | skipTwoFactorControl: boolean = false
119 | ) => {
120 | if ( !( user.username || user.email ) ) {
121 | let e = new Error( 'username or email required' ) as HttpError;
122 | e.responseCode = ResponseCode.BAD_REQUEST;
123 | throw e;
124 | }
125 |
126 | if ( !user.password ) {
127 | let e = new Error( 'password required' ) as HttpError;
128 | e.responseCode = ResponseCode.BAD_REQUEST;
129 | throw e;
130 | }
131 |
132 | const userRepository = new UserRepository( db );
133 |
134 | const username = user.username || user.email;
135 |
136 | let dbUser = await userRepository.findUserByUsernameOrEmail( username );
137 |
138 | // if user is not admin and origin is related with admin clients, do not permit to process
139 | if ( dbUser?.role && dbUser.role !== UserRole.ADMIN && process.env.ADMIN_CLIENT_URLS?.includes( origin ) ) {
140 | let e = new Error( 'Bad Credentials' ) as HttpError;
141 | e.responseCode = ResponseCode.FORBIDDEN;
142 | throw e;
143 | }
144 |
145 | if ( !( await bcrypt.compare( user.password, dbUser.password ) ) ) {
146 | let e = new Error( 'Bad credentials' ) as HttpError;
147 | e.responseCode = ResponseCode.FORBIDDEN;
148 | throw e;
149 | }
150 |
151 | if ( !dbUser.verified ) {
152 | let e = new Error( 'Account not verified' ) as HttpError;
153 | e.responseCode = ResponseCode.FORBIDDEN;
154 | throw e;
155 | }
156 |
157 | if ( !skipTwoFactorControl && dbUser.two_factor_enabled ) {
158 | const environment = new Environment();
159 | const tokenUtil = new TokenUtil( environment.args() );
160 | const twoFactorCodeController = new TwoFactorCodeController();
161 |
162 | const preAuthToken = tokenUtil.generatePreAuthToken( user );
163 |
164 | const twoFactorCode = {
165 | username: dbUser.username,
166 | phoneNumber: dbUser.phone_number,
167 | } as TwoFactorCode;
168 |
169 | const twoFactorRequestResponse = await twoFactorCodeController.request(
170 | db,
171 | messageQueueProvider,
172 | twoFactorCode
173 | );
174 |
175 | return {
176 | preAuthToken,
177 | expiry: twoFactorRequestResponse.expire,
178 | maskedPhoneNumber: this.maskPhoneNumber( dbUser.phone_number ),
179 | };
180 | } else {
181 | const tokenRepository = new TokenRepository( db );
182 |
183 | const genereateTokenResponse = await tokenRepository.generateTokens(
184 | dbUser,
185 | );
186 | return {
187 | accessToken: genereateTokenResponse.accessToken,
188 | refreshToken: genereateTokenResponse.refreshToken,
189 | };
190 | }
191 | };
192 |
193 | twoFactorVerifiedLogin = async ( db: PostgreSqlProvider, user: any ) => {
194 | const tokenRepository = new TokenRepository( db );
195 | const genereateTokenResponse = await tokenRepository.generateTokens( user );
196 | return {
197 | accessToken: genereateTokenResponse.accessToken,
198 | refreshToken: genereateTokenResponse.refreshToken,
199 | };
200 | };
201 |
202 | /**
203 | * logout user
204 | * @param db database
205 | * @param token token
206 | */
207 | logout = async ( db: PostgreSqlProvider, token: string ) => {
208 | const tokenRepository = new TokenRepository( db );
209 | await tokenRepository.deleteToken( token );
210 | };
211 |
212 | /**
213 | * generates and returns new access token
214 | * @param db database
215 | * @param token token
216 | */
217 | token = async ( db: PostgreSqlProvider, token: string ) => {
218 | const tokenRepository = new TokenRepository( db );
219 | await tokenRepository.findToken( token );
220 | const user: any = this.tokenUtil.verifyRefreshToken( token );
221 | return this.tokenUtil.generateAccessToken( user );
222 | };
223 |
224 | /**
225 | * verifies token
226 | * @param db database
227 | * @param token token
228 | */
229 | verify = async (
230 | db: PostgreSqlProvider,
231 | token: string,
232 | ) => {
233 | const user: any = this.tokenUtil.verifyVerificationToken( token );
234 |
235 | const userRepository = new UserRepository( db );
236 | return await userRepository.verifyUser( user.username );
237 | };
238 |
239 | /**
240 | * sends password reset mail
241 | * @param db database
242 | * @param message_queue_provider
243 | * @param username username
244 | * @param languageCode language code
245 | * @param sendEmail don't send email if false
246 | */
247 | forgetPassword = async (
248 | db: PostgreSqlProvider,
249 | message_queue_provider: MessageQueueProvider,
250 | origin: string,
251 | username: string,
252 | languageCode?: string,
253 | sendEmail: boolean = true
254 | ) => {
255 | const userRepository = new UserRepository( db );
256 | const user = await userRepository.findEmailAndPasswordByUsername( username );
257 | const passwordResetToken = this.tokenUtil.generatePasswordResetToken( user );
258 |
259 | if ( sendEmail ) {
260 | const orchestrationChannelTag =
261 | this.environment.args().mqArgs?.orchestrationServerMessageQueueChannel;
262 | const forgetPasswordParams = {
263 | user: user.username,
264 | email: user.email,
265 | passwordResetToken,
266 | clientResetPasswordUrl: origin + '/reset-password'
267 | } as ForgetPasswordMailActionParams;
268 | const message = {
269 | sender: MessageQueueChannelType.AUTH,
270 | receiver: MessageQueueChannelType.MAIL,
271 | message: {
272 | mailType: {
273 | forgetPassword: {
274 | params: forgetPasswordParams,
275 | },
276 | },
277 | language: languageCode,
278 | } as MailActionType,
279 | } as QueueMessage;
280 | await message_queue_provider.publish(
281 | message,
282 | orchestrationChannelTag as string
283 | );
284 | }
285 |
286 | return passwordResetToken;
287 | };
288 |
289 | /**
290 | * verifies password reset token and resets password
291 | * @param db database
292 | * @param user user
293 | * @param token token
294 | */
295 | resetPassword = async ( db: PostgreSqlProvider, user: User, token: string ) => {
296 | if ( !user.password || !user.username ) {
297 | let e = new Error( 'username and password required' ) as HttpError;
298 | e.responseCode = ResponseCode.BAD_REQUEST;
299 | throw e;
300 | }
301 |
302 | user.password = await bcrypt.hash( user.password, 10 );
303 |
304 | const userRepository = new UserRepository( db );
305 | const dbUser = await userRepository.findEmailAndPasswordByUsername(
306 | user.username
307 | );
308 |
309 | this.tokenUtil.verifyPasswordResetToken( token, dbUser.password );
310 | await userRepository.updateByUsername( user );
311 | };
312 |
313 | /**
314 | * delete user
315 | * @param db database
316 | * @param currentUser current user
317 | * @param user user
318 | */
319 | deleteUser = async (
320 | db: PostgreSqlProvider,
321 | currentUser: string,
322 | user: User
323 | ) => {
324 | if ( !user.username ) {
325 | let e = new Error( 'username required' ) as HttpError;
326 | e.responseCode = ResponseCode.BAD_REQUEST;
327 | throw e;
328 | } else if ( currentUser === user.username ) {
329 | let e = new Error( 'you cannot delete yourself' ) as HttpError;
330 | e.responseCode = ResponseCode.BAD_REQUEST;
331 | throw e;
332 | }
333 |
334 | const userRepository = new UserRepository( db );
335 |
336 | await userRepository.deleteUserByUsername( user.username );
337 | };
338 |
339 | getSubmittedPhoneNumber = async (
340 | db: PostgreSqlProvider,
341 | username: string
342 | ) => {
343 | const userRepository = new UserRepository( db );
344 | const phoneNumberResponse =
345 | await userRepository.findVerifiedPhoneNumberByUsername( username );
346 |
347 | let phoneNumber;
348 | if (
349 | phoneNumberResponse?.length > 0 &&
350 | phoneNumberResponse[ 0 ].phone_number
351 | ) {
352 | phoneNumber = this.maskPhoneNumber( phoneNumberResponse[ 0 ].phone_number );
353 | }
354 |
355 | return phoneNumber;
356 | };
357 |
358 | deleteSubmittedPhoneNumber = async (
359 | db: PostgreSqlProvider,
360 | username: string
361 | ) => {
362 | const userRepository = new UserRepository( db );
363 |
364 | await userRepository.deletedSubmittedPhoneNumberByUsername( username );
365 | };
366 |
367 | getUsers = async (
368 | db: PostgreSqlProvider,
369 | role?: string,
370 | verified?: any,
371 | oauth?: any,
372 | twoFA?: any,
373 | username?: string,
374 | filters?: QueryFilters,
375 | ) => {
376 | const userRepository = new UserRepository( db );
377 |
378 | if ( role === 'All' ) {
379 | role = '';
380 | }
381 |
382 | if ( verified === 'true' ) {
383 | verified = true;
384 | } else if ( verified === 'false' ) {
385 | verified = false;
386 | }
387 |
388 | if ( twoFA === 'true' ) {
389 | twoFA = true;
390 | } else if ( twoFA === 'false' ) {
391 | twoFA = false;
392 | }
393 |
394 | let offset = 0;
395 | let limit = 20;
396 |
397 | if ( filters ) {
398 | if ( filters.offset ) {
399 | offset = filters.offset;
400 | }
401 |
402 | if ( filters.limit ) {
403 | limit = filters.limit;
404 | }
405 | }
406 |
407 | let users: any[] = [];
408 | let count: any;
409 |
410 | users = await userRepository.getAllUsers( role ?? '', verified, twoFA, oauth, username ?? '', offset, limit );
411 | count = +( await userRepository.getAllUsersCount( role ?? '', verified, twoFA, oauth, username ?? '' ) ).count ?? 0;
412 |
413 | return { users, meta: { offset, limit, count } };
414 | };
415 |
416 | private maskPhoneNumber( number: string ): string {
417 | let maskedNumber = '';
418 | for ( let i = 0; i < number.length; i++ ) {
419 | if ( i > number.length - 3 ) {
420 | maskedNumber += number.charAt( i );
421 | } else {
422 | maskedNumber += '*';
423 | }
424 | }
425 | return maskedNumber;
426 | }
427 | }
428 |
429 |
--------------------------------------------------------------------------------
/app/controller/info.controller.ts:
--------------------------------------------------------------------------------
1 | import { PostgreSqlProvider, TokenUtil } from '@open-template-hub/common';
2 | import { Environment } from '../../environment';
3 | import { UserRepository } from '../repository/user.repository';
4 |
5 | export class InfoController {
6 | /**
7 | * gets user details by token
8 | * @param db database
9 | * @param token token
10 | */
11 | me = async ( db: PostgreSqlProvider, token: string ) => {
12 | const environment = new Environment();
13 | const tokenUtil = new TokenUtil( environment.args() );
14 | const user: any = tokenUtil.verifyAccessToken( token );
15 |
16 | const userRepository = new UserRepository( db );
17 | return userRepository.findEmailByUsername( user.username );
18 | };
19 |
20 | other = async ( db: PostgreSqlProvider, username: string ) => {
21 | const userRepository = new UserRepository( db );
22 | return userRepository.findEmailByUsername( username );
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/app/controller/social-login.controller.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds social login controller
3 | */
4 |
5 | import {
6 | BuilderUtil,
7 | EncryptionUtil,
8 | HttpError,
9 | MessageQueueProvider,
10 | ParserUtil,
11 | PostgreSqlProvider,
12 | ResponseCode,
13 | TokenUtil,
14 | User,
15 | UserRole,
16 | } from '@open-template-hub/common';
17 |
18 | import axios from 'axios';
19 | import bcrypt from 'bcrypt';
20 | import capitalize from 'capitalize';
21 | import OAuth from 'oauth-1.0a';
22 | import { v4 as uuid } from 'uuid';
23 | import { Environment } from '../../environment';
24 | import { AuthToken } from '../interface/auth-token.interface';
25 | import { TwoFactorCode } from '../interface/two-factor-code.interface';
26 | import { SocialLoginRepository } from '../repository/social-login.repository';
27 | import { TokenRepository } from '../repository/token.repository';
28 | import { UserRepository } from '../repository/user.repository';
29 | import { TwoFactorCodeController } from './two-factor.controller';
30 |
31 | export class SocialLoginController {
32 |
33 | builder: BuilderUtil;
34 | parser: ParserUtil;
35 | environment: Environment;
36 | encryptionUtil: EncryptionUtil;
37 | tokenUtil: TokenUtil;
38 |
39 | constructor() {
40 | this.builder = new BuilderUtil();
41 | this.parser = new ParserUtil();
42 | this.environment = new Environment();
43 | this.encryptionUtil = new EncryptionUtil( this.environment.args() );
44 | this.tokenUtil = new TokenUtil( this.environment.args() );
45 | }
46 |
47 | /**
48 | * gets social login url
49 | * @param db database
50 | * @param data social login data
51 | */
52 | loginUrl = async ( db: PostgreSqlProvider, data: any ) => {
53 | let loginUrl = '';
54 | if ( !data.key ) {
55 | let e = new Error( 'key required' ) as HttpError;
56 | e.responseCode = ResponseCode.BAD_REQUEST;
57 | throw e;
58 | }
59 |
60 | const socialLoginRepository = new SocialLoginRepository( db );
61 | let socialLoginParams = await socialLoginRepository.findSocialLoginByKey(
62 | data.key
63 | );
64 |
65 | // if oauth version 2
66 | if ( socialLoginParams.v2Config ) {
67 | const params = [
68 | socialLoginParams.v2Config.client_id,
69 | data.state,
70 | socialLoginParams.v2Config.redirect_uri,
71 | ];
72 | loginUrl = this.builder.buildUrl(
73 | socialLoginParams.v2Config.login_uri,
74 | params
75 | );
76 | } else if ( socialLoginParams.v1Config ) {
77 | const oAuthRequestToken = await this.getOAuthRequestToken(
78 | socialLoginParams.v1Config
79 | );
80 |
81 | loginUrl = this.builder.buildUrl( socialLoginParams.v1Config.login_uri, [
82 | oAuthRequestToken as string,
83 | ] );
84 | }
85 |
86 | return loginUrl;
87 | };
88 |
89 | /**
90 | * login user
91 | * @param db database
92 | * @param data social login data
93 | */
94 | login = async (
95 | db: PostgreSqlProvider,
96 | messageQueueProvider: MessageQueueProvider,
97 | data: any
98 | ) => {
99 | if ( !data.key ) {
100 | let e = new Error( 'key required' ) as HttpError;
101 | e.responseCode = ResponseCode.BAD_REQUEST;
102 | throw e;
103 | }
104 |
105 | const socialLoginRepository = new SocialLoginRepository( db );
106 | const socialLoginParams = await socialLoginRepository.findSocialLoginByKey(
107 | data.key
108 | );
109 |
110 | if ( socialLoginParams.v2Config ) {
111 | return this.loginForOauthV2(
112 | db,
113 | messageQueueProvider,
114 | socialLoginParams.v2Config,
115 | data
116 | );
117 | } else if ( socialLoginParams.v1Config ) {
118 | return this.loginForOauthV1(
119 | db,
120 | messageQueueProvider,
121 | socialLoginParams.v1Config,
122 | data
123 | );
124 | } else {
125 | let e = new Error( 'config not found!' ) as HttpError;
126 | e.responseCode = ResponseCode.INTERNAL_SERVER_ERROR;
127 | throw e;
128 | }
129 | };
130 |
131 | /**
132 | * login user with access token
133 | * @param db database
134 | * @param data social login data
135 | */
136 | loginWithAccessToken = async (
137 | db: PostgreSqlProvider,
138 | messageQueueProvider: MessageQueueProvider,
139 | data: any
140 | ): Promise => {
141 | try {
142 | const socialLoginRepository = new SocialLoginRepository( db );
143 | const socialLoginParams =
144 | await socialLoginRepository.findSocialLoginByKey( data.key );
145 |
146 | if ( socialLoginParams.v2Config ) {
147 | let accessTokenData = {
148 | token: data.accessToken,
149 | type: data.tokenType,
150 | };
151 |
152 | return await this.loginWithAccessTokenForOauthV2(
153 | db,
154 | messageQueueProvider,
155 | accessTokenData,
156 | socialLoginParams.v2Config,
157 | data
158 | );
159 | } else {
160 | throw new Error( 'Method Not Implemented' );
161 | }
162 | } catch ( e: any ) {
163 | console.error( e );
164 | e.responseCode = ResponseCode.FORBIDDEN;
165 | throw e;
166 | }
167 | };
168 |
169 | /**
170 | * login for oauth v1
171 | * @param db database
172 | * @param config configuration
173 | * @param params parameters
174 | */
175 | loginForOauthV1 = async (
176 | db: PostgreSqlProvider,
177 | messageQueueProvider: MessageQueueProvider,
178 | config: any,
179 | params: any,
180 | skipTwoFactorControl: boolean = false
181 | ) => {
182 | let accessTokenData = await this.getAccessTokenDataForOauthV1(
183 | config,
184 | params
185 | );
186 | if ( !accessTokenData.token ) {
187 | let e = new Error( 'Access token couldn\'t obtained' ) as HttpError;
188 | e.responseCode = ResponseCode.BAD_REQUEST;
189 | console.error( e );
190 | throw e;
191 | }
192 |
193 | let userData = accessTokenData.userData;
194 | if ( !userData.external_user_id ) {
195 | let e = new Error( 'User data couldn\'t obtained' ) as HttpError;
196 | e.responseCode = ResponseCode.BAD_REQUEST;
197 | console.error( e );
198 | throw e;
199 | }
200 |
201 | return this.loginUserWithUserData(
202 | db,
203 | messageQueueProvider,
204 | params.key,
205 | userData,
206 | skipTwoFactorControl
207 | );
208 | };
209 |
210 | /**
211 | * login for oauth v2
212 | * @param db database
213 | * @param messageQueueProvider
214 | * @param config configuration
215 | * @param params parameters
216 | * @param skipTwoFactorControl
217 | */
218 | loginForOauthV2 = async (
219 | db: PostgreSqlProvider,
220 | messageQueueProvider: MessageQueueProvider,
221 | config: any,
222 | params: any,
223 | skipTwoFactorControl: boolean = false
224 | ) => {
225 | let accessTokenData = await this.getAccessTokenDataForOauthV2(
226 | config,
227 | params
228 | );
229 | if ( !accessTokenData.token ) {
230 | let e = new Error( 'Access token couldn\'t obtained' ) as HttpError;
231 | e.responseCode = ResponseCode.BAD_REQUEST;
232 | console.error( e );
233 | throw e;
234 | }
235 |
236 | return this.loginWithAccessTokenForOauthV2(
237 | db,
238 | messageQueueProvider,
239 | accessTokenData,
240 | config,
241 | params,
242 | skipTwoFactorControl
243 | );
244 | };
245 |
246 | /**
247 | * login for oauth v2 with access token
248 | * @param db database
249 | * @param accessTokenData access token data
250 | * @param config configuration
251 | * @param params parameters
252 | */
253 | loginWithAccessTokenForOauthV2 = async (
254 | db: PostgreSqlProvider,
255 | messageQueueProvider: MessageQueueProvider,
256 | accessTokenData: any,
257 | config: any,
258 | params: any,
259 | skipTwoFactorControl: boolean = false
260 | ) => {
261 | let userData = await this.getUserDataWithAccessToken(
262 | accessTokenData,
263 | config
264 | );
265 | if ( !userData.external_user_id ) {
266 | let e = new Error( 'User data couldn\'t obtained' ) as HttpError;
267 | e.responseCode = ResponseCode.BAD_REQUEST;
268 | console.error( e );
269 | throw e;
270 | }
271 |
272 | return this.loginUserWithUserData(
273 | db,
274 | messageQueueProvider,
275 | params.key,
276 | userData,
277 | skipTwoFactorControl
278 | );
279 | };
280 |
281 | /**
282 | * gets user data with access token
283 | * @param accessTokenData access token data
284 | * @param config config
285 | */
286 | getUserDataWithAccessToken = async ( accessTokenData: any, config: any ) => {
287 | // getting user data with access token
288 | let userDataUrl = config.user_data_uri;
289 | let headers = {
290 | Accept: 'application/json',
291 | 'User-Agent':
292 | 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0',
293 | Authorization: '',
294 | 'Client-ID': config.client_id,
295 | };
296 |
297 | if ( config.requested_with_auth_header ) {
298 | // default authorization token type
299 | const tokenType = accessTokenData.type
300 | ? capitalize( accessTokenData.type )
301 | : 'Bearer';
302 | headers = {
303 | Accept: 'application/json',
304 | 'User-Agent':
305 | 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0',
306 | Authorization: tokenType + ' ' + accessTokenData.token,
307 | 'Client-ID': config.client_id,
308 | };
309 | } else {
310 | userDataUrl = this.builder.buildUrl( config.user_data_uri, [
311 | accessTokenData.token,
312 | ] );
313 | }
314 |
315 | const userDataResponse = await axios.get( `${ userDataUrl }`, {
316 | headers,
317 | } );
318 |
319 | const external_user_id = this.parser.getJsonValue(
320 | userDataResponse.data,
321 | config.external_user_id_json_field_path
322 | );
323 | const external_user_email = this.parser.getJsonValue(
324 | userDataResponse.data,
325 | config.external_user_email_json_field_path
326 | );
327 | const external_username = this.parser.getJsonValue(
328 | userDataResponse.data,
329 | config.external_username_json_field_path
330 | );
331 |
332 | return {
333 | external_user_id: external_user_id,
334 | external_user_email: external_user_email,
335 | external_username: external_username,
336 | };
337 | };
338 |
339 | /**
340 | * gets access token data for oauth v1
341 | * @param config configuration
342 | * @param params parameters
343 | */
344 | getAccessTokenDataForOauthV1 = async ( config: any, params: any ) => {
345 | const accessTokenParams = [ params.oauth_token, params.oauth_verifier ];
346 | const accessTokenUrl = this.builder.buildUrl(
347 | config.access_token_uri,
348 | accessTokenParams
349 | );
350 |
351 | let accessTokenResponse: any;
352 |
353 | if ( config.access_token_request_method === 'GET' ) {
354 | accessTokenResponse = await axios.get( `${ accessTokenUrl }`, {} );
355 | } else if ( config.access_token_request_method === 'POST' ) {
356 | accessTokenResponse = await axios.post( `${ accessTokenUrl }`, {} );
357 | }
358 |
359 | const urlParams = new URLSearchParams( accessTokenResponse.data );
360 |
361 | let oAuthTokenParam = urlParams.get(
362 | config.access_token_query_param_field_path
363 | );
364 |
365 | const userData = {
366 | external_user_id: urlParams.get(
367 | config.external_user_id_query_param_field_path
368 | ),
369 | external_user_email: urlParams.get(
370 | config.external_user_email_query_param_field_path
371 | ),
372 | external_username: urlParams.get(
373 | config.external_username_query_param_field_path
374 | ),
375 | };
376 |
377 | return {
378 | token: oAuthTokenParam,
379 | userData: userData,
380 | };
381 | };
382 |
383 | /**
384 | * gets access token data for oauth v2
385 | * @param config configuration
386 | * @param params parameters
387 | */
388 | getAccessTokenDataForOauthV2 = async ( config: any, params: any ) => {
389 | const headers = {
390 | Accept: 'application/json',
391 | 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
392 | };
393 | const accessTokenParams = [
394 | config.client_id,
395 | config.client_secret,
396 | config.redirect_uri,
397 | params.code,
398 | params.state,
399 | ];
400 | const accessTokenUrl = this.builder.buildUrl(
401 | config.access_token_uri,
402 | accessTokenParams
403 | );
404 |
405 | let accessTokenResponse: any;
406 |
407 | if ( config.access_token_request_method === 'GET' ) {
408 | accessTokenResponse = await axios.get( `${ accessTokenUrl }`, {
409 | headers,
410 | } );
411 | } else if ( config.access_token_request_method === 'POST' ) {
412 | accessTokenResponse = await axios.post(
413 | `${ accessTokenUrl }`,
414 | {},
415 | { headers }
416 | );
417 | }
418 |
419 | const accessToken = this.parser.getJsonValue(
420 | accessTokenResponse.data,
421 | config.access_token_json_field_path
422 | );
423 |
424 | let tokenType = params.tokenType;
425 |
426 | if ( !tokenType ) {
427 | tokenType = this.parser.getJsonValue(
428 | accessTokenResponse.data,
429 | config.token_type_json_field_path
430 | );
431 | }
432 |
433 | return {
434 | token: accessToken,
435 | type: tokenType,
436 | };
437 | };
438 |
439 | /**
440 | * login user with user data
441 | * @param db database
442 | * @param messageQueueProvider
443 | * @param key social login key
444 | * @param userData user data
445 | * @param skipTwoFactorControl
446 | */
447 | loginUserWithUserData = async (
448 | db: PostgreSqlProvider,
449 | messageQueueProvider: MessageQueueProvider,
450 | key: string,
451 | userData: any,
452 | skipTwoFactorControl: boolean
453 | ): Promise => {
454 | // checking social login mapping to determine if signup or login
455 | const socialLoginRepository = new SocialLoginRepository( db );
456 | let socialLoginUser =
457 | await socialLoginRepository.findMappingDataByExternalUserId(
458 | key,
459 | userData.external_user_id
460 | );
461 |
462 | const tokenRepository = new TokenRepository( db );
463 |
464 | if ( socialLoginUser ) {
465 | // login user, generate token
466 |
467 | const userRepository = new UserRepository( db );
468 | const username = socialLoginUser.username;
469 | let dbUser = await userRepository.findUserByUsernameOrEmail( username );
470 |
471 | if ( !skipTwoFactorControl && dbUser.two_factor_enabled ) {
472 | const environment = new Environment();
473 | const tokenUtil = new TokenUtil( environment.args() );
474 | const twoFactorCodeController = new TwoFactorCodeController();
475 |
476 | const user = {
477 | username,
478 | } as User;
479 |
480 | const preAuthToken = tokenUtil.generatePreAuthToken( user );
481 |
482 | const twoFactorCode = {
483 | username: dbUser.username,
484 | phoneNumber: dbUser.phone_number,
485 | } as TwoFactorCode;
486 |
487 | const twoFactorRequestResponse = await twoFactorCodeController.request(
488 | db,
489 | messageQueueProvider,
490 | twoFactorCode
491 | );
492 |
493 | return {
494 | preAuthToken,
495 | expiry: twoFactorRequestResponse.expire,
496 | maskedPhoneNumber: this.maskPhoneNumber( dbUser.phone_number ),
497 | } as any;
498 | } else {
499 | return tokenRepository.generateTokens( socialLoginUser );
500 | }
501 | } else {
502 | // signup user and generate token
503 | const autoGeneratedUserName = uuid();
504 | const autoGeneratedPassword = uuid();
505 |
506 | socialLoginUser = {
507 | username: autoGeneratedUserName,
508 | password: autoGeneratedPassword,
509 | email: userData.external_user_email,
510 | role: UserRole.DEFAULT,
511 | };
512 |
513 | await this.signup( db, socialLoginUser );
514 | await socialLoginRepository.insertSocialLoginMapping(
515 | key,
516 | userData.external_user_id,
517 | userData.external_username,
518 | userData.external_user_email,
519 | autoGeneratedUserName
520 | );
521 |
522 | return tokenRepository.generateTokens( socialLoginUser );
523 | }
524 | };
525 |
526 | /**
527 | * sign up user
528 | * @param db database
529 | * @param user user
530 | */
531 | signup = async ( db: PostgreSqlProvider, user: User ) => {
532 | const hashedPassword = await bcrypt.hash( user.password, 10 );
533 |
534 | const userRepository = new UserRepository( db );
535 | await userRepository.insertUser( {
536 | username: user.username,
537 | password: hashedPassword,
538 | role: UserRole.DEFAULT,
539 | } as User );
540 | await userRepository.verifyUser( user.username );
541 | };
542 |
543 | /**
544 | * gets oauth request token
545 | * @param config configuration
546 | */
547 | getOAuthRequestToken = async ( config: any ) => {
548 | const oauth = new OAuth( {
549 | consumer: {
550 | key: config.client_id,
551 | secret: config.client_secret,
552 | },
553 | signature_method: 'HMAC-SHA1',
554 | hash_function: this.encryptionUtil.hash_function_sha1,
555 | } );
556 |
557 | const request_data = {
558 | url: config.request_token_uri,
559 | method: 'POST',
560 | data: { oauth_callback: config.redirect_uri },
561 | };
562 |
563 | let headers = oauth.toHeader( oauth.authorize( request_data ) );
564 |
565 | let axiosHeader: Record = {};
566 | axiosHeader.Authorization = headers.Authorization;
567 |
568 | const oAuthRequestTokenResponse: any = await axios.post(
569 | `${ config.request_token_uri }`,
570 | {},
571 | { headers: axiosHeader }
572 | );
573 |
574 | const urlParams = new URLSearchParams( oAuthRequestTokenResponse.data );
575 |
576 | if ( urlParams.has( 'oauth_token' ) ) {
577 | return urlParams.get( 'oauth_token' );
578 | }
579 |
580 | return '';
581 | };
582 |
583 | private maskPhoneNumber( number: string ): string {
584 | let maskedNumber = '';
585 | for ( let i = 0; i < number.length; i++ ) {
586 | if ( i > number.length - 3 ) {
587 | maskedNumber += number.charAt( i );
588 | } else {
589 | maskedNumber += '*';
590 | }
591 | }
592 | return maskedNumber;
593 | }
594 | }
595 |
--------------------------------------------------------------------------------
/app/controller/two-factor.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | HttpError,
3 | MessageQueueChannelType,
4 | MessageQueueProvider,
5 | PostgreSqlProvider,
6 | QueueMessage,
7 | ResponseCode,
8 | SmsActionType,
9 | TokenUtil,
10 | TwoFactorCodeRequestParams,
11 | } from '@open-template-hub/common';
12 | import crypto from 'crypto';
13 | import { Environment } from '../../environment';
14 | import { TwoFactorCode } from '../interface/two-factor-code.interface';
15 | import { TwoFactorCodeRepository } from '../repository/two-factor.repository';
16 | import { UserRepository } from '../repository/user.repository';
17 | import { AuthController } from './auth.controller';
18 |
19 | export class TwoFactorCodeController {
20 |
21 | environment: Environment;
22 |
23 | constructor() {
24 | this.environment = new Environment();
25 | }
26 |
27 | request = async (
28 | db: PostgreSqlProvider,
29 | messageQueueProvider: MessageQueueProvider,
30 | twoFactorCode: TwoFactorCode,
31 | ) => {
32 | let twoFactorCodeExpire = +( this.environment.args().twoFactorCodeArgs.twoFactorCodeExpire );
33 | let twoFactorCodeLength = +( this.environment.args().twoFactorCodeArgs.twoFactorCodeLength );
34 | let twoFactorCodeType = ( this.environment.args().twoFactorCodeArgs.twoFactorCodeType );
35 |
36 | let expiry = new Date();
37 | expiry.setSeconds( expiry.getSeconds() + twoFactorCodeExpire );
38 |
39 | twoFactorCode.expiry = expiry.getTime().toString();
40 | twoFactorCode.code = this.randomStringGenerate( twoFactorCodeLength, twoFactorCodeType );
41 |
42 | const twoFactorRepository = new TwoFactorCodeRepository( db );
43 |
44 | await twoFactorRepository.insertTwoFactorCode(
45 | twoFactorCode.username,
46 | twoFactorCode.phoneNumber,
47 | twoFactorCode.code,
48 | twoFactorCode.expiry
49 | );
50 |
51 | await this.sendTwoFactorRequestToQueue(
52 | messageQueueProvider,
53 | twoFactorCode
54 | );
55 |
56 | return { expire: twoFactorCode.expiry };
57 | };
58 |
59 | verify = async (
60 | db: PostgreSqlProvider,
61 | username: string,
62 | twoFactorCode: string,
63 | isInitialVerification: boolean = true
64 | ) => {
65 | const twoFactorCodeRepository = new TwoFactorCodeRepository( db );
66 |
67 | const twoFactorCodeResponse = await twoFactorCodeRepository.findTwoFactorCodeByUsername(
68 | username
69 | );
70 |
71 | // expiry check
72 | if ( twoFactorCodeResponse.expiry < new Date().getTime().toString() ) {
73 | let e = new Error( 'Code Expired' ) as HttpError;
74 | e.responseCode = ResponseCode.BAD_REQUEST;
75 | console.error( e );
76 | throw e;
77 | }
78 |
79 | // Code Check
80 | if ( twoFactorCodeResponse.code !== twoFactorCode ) {
81 | let e = new Error( 'Invalid Code' ) as HttpError;
82 | e.responseCode = ResponseCode.BAD_REQUEST;
83 | console.error( e );
84 | throw e;
85 | }
86 |
87 | if ( isInitialVerification ) {
88 | const userRepository = new UserRepository( db );
89 |
90 | await userRepository.addPhoneNumberToUser( twoFactorCodeResponse.phone_number, username );
91 | await userRepository.updateTwoFactorEnabled( true, username );
92 | }
93 | };
94 |
95 | loginVerify = async (
96 | db: PostgreSqlProvider,
97 | code: string,
98 | preAuthToken: string
99 | ) => {
100 | const environment = new Environment();
101 | const tokenUtil = new TokenUtil( environment.args() );
102 |
103 | const decrpytedPreAuthToken = tokenUtil.verifyPreAuthToken( preAuthToken ) as any;
104 |
105 | if ( !decrpytedPreAuthToken.username ) {
106 | let e = new Error( 'Invalid token' ) as HttpError;
107 | e.responseCode = ResponseCode.BAD_REQUEST;
108 | console.error( e );
109 | throw e;
110 | }
111 |
112 | await this.verify( db, decrpytedPreAuthToken.username as string, code, false );
113 |
114 | const userRepository = new UserRepository( db );
115 | let dbUser = await userRepository.findUserByUsernameOrEmail( decrpytedPreAuthToken.username as string );
116 |
117 | const authController = new AuthController();
118 | return authController.twoFactorVerifiedLogin( db, dbUser );
119 | };
120 |
121 | private randomString( length: number, chars: string ): string {
122 | const charsLength = chars.length;
123 | if ( charsLength > 256 ) {
124 | throw new Error( 'Argument \'chars\' should not have more than 256 characters'
125 | + ', otherwise unpredictability will be broken' );
126 | }
127 |
128 | const randomBytes = crypto.randomBytes( length );
129 | let result = new Array( length );
130 |
131 | let cursor = 0;
132 | for ( let i = 0; i < length; i++ ) {
133 | cursor += randomBytes[ i ];
134 | result[ i ] = chars[ cursor % charsLength ];
135 | }
136 |
137 | return result.join( '' );
138 | }
139 |
140 | private randomStringGenerate( length: number, codeType: string ): string {
141 | let chars: string;
142 |
143 | if ( codeType === 'numeric' ) {
144 | chars = '0123456789';
145 | } else {
146 | chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
147 | }
148 |
149 | return this.randomString( length, chars );
150 | }
151 |
152 | private async sendTwoFactorRequestToQueue(
153 | messageQueueProvider: MessageQueueProvider,
154 | twoFactorCode: TwoFactorCode
155 | ) {
156 | const orchestrationChannelTag =
157 | this.environment.args().mqArgs?.orchestrationServerMessageQueueChannel;
158 |
159 | const twoFactorRequestParams = {
160 | username: twoFactorCode.username,
161 | phoneNumber: twoFactorCode.phoneNumber,
162 | twoFactorCode: twoFactorCode.code
163 | } as TwoFactorCodeRequestParams;
164 |
165 | const message = {
166 | sender: MessageQueueChannelType.AUTH,
167 | receiver: MessageQueueChannelType.SMS,
168 | message: {
169 | smsType: {
170 | twoFactorCodeRequest: {
171 | params: twoFactorRequestParams,
172 | }
173 | },
174 | language: twoFactorCode.languageCode
175 | } as SmsActionType,
176 | } as QueueMessage;
177 |
178 | await messageQueueProvider.publish(
179 | message,
180 | orchestrationChannelTag as string
181 | );
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/app/enum/social-login.enum.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/open-template-hub/auth-server-template/ea72845fd36a64138613ad92caebbc0e2dda176e/app/enum/social-login.enum.ts
--------------------------------------------------------------------------------
/app/enum/user-role.enum.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds user roles
3 | */
4 |
5 | export enum UserRole {
6 | USER = 'USER',
7 | ADMIN = 'ADMIN',
8 | }
9 |
--------------------------------------------------------------------------------
/app/interface/auth-token.interface.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds auth token interface
3 | */
4 |
5 | export interface AuthToken {
6 | refreshToken: string;
7 | accessToken: string;
8 | }
9 |
--------------------------------------------------------------------------------
/app/interface/two-factor-code.interface.ts:
--------------------------------------------------------------------------------
1 | export interface TwoFactorCode {
2 | username: string;
3 | phoneNumber: string;
4 | code?: string;
5 | expiry?: string;
6 | languageCode?: string;
7 | }
8 |
--------------------------------------------------------------------------------
/app/repository/social-login.repository.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds social login repository
3 | */
4 |
5 | import { PostgreSqlProvider } from '@open-template-hub/common';
6 |
7 | export class SocialLoginRepository {
8 | constructor( private readonly provider: PostgreSqlProvider ) {
9 | }
10 |
11 | /**
12 | * gets social login configuration by key
13 | * @param key key
14 | */
15 | findSocialLoginByKey = async ( key: string ) => {
16 | let res;
17 | try {
18 | let v1Join =
19 | ' INNER JOIN oauth_v1_config_params CP ON CP.social_login_key = SL.social_login_key';
20 | let v2Join =
21 | ' INNER JOIN oauth_v2_config_params CP ON CP.social_login_key = SL.social_login_key';
22 |
23 | let selectClause = 'SELECT SL.*, CP.* FROM social_logins SL';
24 | let whereClause = ' WHERE SL.social_login_key = $1';
25 |
26 | let v1 = await this.provider.query( selectClause + v1Join + whereClause, [
27 | key,
28 | ] );
29 | let v2 = await this.provider.query( selectClause + v2Join + whereClause, [
30 | key,
31 | ] );
32 |
33 | res = {
34 | v1Config: v1.rowCount > 0 ? v1.rows[ 0 ] : undefined,
35 | v2Config: v2.rowCount > 0 ? v2.rows[ 0 ] : undefined,
36 | };
37 | } catch ( error ) {
38 | console.error( error );
39 | throw error;
40 | }
41 | return res;
42 | };
43 |
44 | /**
45 | * gets mapping data by external user id
46 | * @param key key
47 | * @param userId user id
48 | */
49 | findMappingDataByExternalUserId = async ( key: string, userId: string ) => {
50 | let res;
51 | try {
52 | res = await this.provider.query(
53 | 'SELECT users.username, users.role FROM social_login_mappings LEFT JOIN users ON users.username = social_login_mappings.username WHERE social_login_key LIKE $1 and external_user_id LIKE $2',
54 | [ key, userId ]
55 | );
56 | } catch ( error ) {
57 | console.error( error );
58 | throw error;
59 | }
60 |
61 | return res.rows[ 0 ];
62 | };
63 |
64 | /**
65 | * creates social login mapping
66 | * @param social_login_key social login key
67 | * @param external_user_id external user id
68 | * @param external_username external username
69 | * @param external_user_email external user email
70 | * @param autoGeneratedUserName auto generated username
71 | */
72 | insertSocialLoginMapping = async (
73 | social_login_key: string,
74 | external_user_id: string,
75 | external_username: string,
76 | external_user_email: string,
77 | autoGeneratedUserName: string
78 | ) => {
79 | try {
80 | await this.provider.query(
81 | 'INSERT INTO social_login_mappings(social_login_key, external_user_id, external_username, external_user_email, username) VALUES($1, $2, $3, $4, $5)',
82 | [
83 | social_login_key,
84 | external_user_id,
85 | external_username,
86 | external_user_email,
87 | autoGeneratedUserName,
88 | ]
89 | );
90 | } catch ( error ) {
91 | console.error( error );
92 | throw error;
93 | }
94 | };
95 |
96 | getAllUsersByKey = async (
97 | username: string,
98 | socialLoginKey: string,
99 | offset: number,
100 | limit: number
101 | ) => {
102 | let response;
103 |
104 | try {
105 | response = await this.provider.query(
106 | 'SELECT social_login_key, username, external_user_email, social_login_mappings.social_login_key FROM social_login_mappings WHERE social_login_key = $1 and (username LIKE $2 or external_user_email LIKE $3) ORDER BY username OFFSET $4 LIMIT $5',
107 | [ socialLoginKey, '%' + username + '%', '%' + username + '%', offset, limit ]
108 | );
109 | } catch ( error ) {
110 | console.error( error );
111 | throw error;
112 | }
113 |
114 | return response.rows;
115 | };
116 |
117 | getAllUsersByKeyCount = async (
118 | username: string,
119 | socialLoginKey: string
120 | ) => {
121 | let response;
122 |
123 | try {
124 | response = await this.provider.query(
125 | 'SELECT COUNT(*) FROM social_login_mappings WHERE social_login_key = $1 and (username LIKE $2 or external_user_email LIKE $3)',
126 | [ socialLoginKey, '%' + username + '%', '%' + username + '%' ]
127 | );
128 | } catch ( error ) {
129 | console.error( error );
130 | throw error;
131 | }
132 |
133 | return response.rows;
134 | };
135 | }
136 |
--------------------------------------------------------------------------------
/app/repository/token.repository.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds token repository
3 | */
4 |
5 | import { HttpError, PostgreSqlProvider, ResponseCode, TokenUtil, User } from '@open-template-hub/common';
6 | import { Environment } from '../../environment';
7 | import { AuthToken } from '../interface/auth-token.interface';
8 |
9 | export class TokenRepository {
10 | constructor( private readonly provider: PostgreSqlProvider ) {
11 | }
12 |
13 | /**
14 | * generates access and refresh tokens
15 | * @param user user
16 | */
17 | generateTokens = async ( user: User ): Promise => {
18 | const environment = new Environment();
19 | const tokenUtil = new TokenUtil( environment.args() );
20 | const accessToken = tokenUtil.generateAccessToken( user );
21 | let refreshToken = tokenUtil.generateRefreshToken( user );
22 |
23 | await this.insertToken( {
24 | token: refreshToken.token,
25 | expireAt: new Date( refreshToken.exp * 1000 ),
26 | } );
27 |
28 | return {
29 | accessToken: accessToken,
30 | refreshToken: refreshToken.token,
31 | } as AuthToken;
32 | };
33 |
34 | /**
35 | * saves token
36 | * @param token token
37 | */
38 | insertToken = async ( token: any ) => {
39 | let res;
40 | try {
41 | const isTokenExists = await this.isTokenExists( token.token );
42 |
43 | if ( !isTokenExists ) {
44 | res = await this.provider.query(
45 | 'INSERT INTO tokens(token, expire_date) VALUES($1, $2)',
46 | [ token.token, token.expireAt ]
47 | );
48 | } else {
49 | res = await this.provider.query(
50 | 'UPDATE tokens SET expire_date = $2 WHERE token = $1',
51 | [ token.token, token.expireAt ]
52 | );
53 | }
54 | } catch ( error ) {
55 | console.error( error );
56 | throw error;
57 | }
58 | return res.rows[ 0 ];
59 | };
60 |
61 | /**
62 | * deletes token
63 | * @param token token
64 | */
65 | deleteToken = async ( token: any ) => {
66 | let res;
67 | try {
68 | res = await this.provider.query(
69 | 'DELETE FROM tokens WHERE token LIKE $1',
70 | [ token ]
71 | );
72 | } catch ( error ) {
73 | console.error( error );
74 | throw error;
75 | }
76 | return res.rows[ 0 ];
77 | };
78 |
79 | /**
80 | * gets token
81 | * @param token token
82 | */
83 | findToken = async ( token: any ) => {
84 | let res;
85 | try {
86 | res = await this.provider.query(
87 | 'SELECT token FROM tokens WHERE token = $1',
88 | [ token ]
89 | );
90 | } catch ( error ) {
91 | console.error( error );
92 | throw error;
93 | }
94 |
95 | if ( res.rows.length === 0 ) {
96 | let e = new Error( 'invalid token' ) as HttpError;
97 | e.responseCode = ResponseCode.UNAUTHORIZED;
98 | throw e;
99 | } else if ( res.rows.length > 1 ) {
100 | console.error( 'ambiguous token' );
101 | let e = new Error( 'internal server error' ) as HttpError;
102 | e.responseCode = ResponseCode.INTERNAL_SERVER_ERROR;
103 | throw e;
104 | }
105 |
106 | return res.rows[ 0 ];
107 | };
108 |
109 | /**
110 | * is token exists
111 | * @param token token
112 | */
113 | isTokenExists = async ( token: any ) => {
114 | let res;
115 | try {
116 | res = await this.provider.query(
117 | 'SELECT token FROM tokens WHERE token = $1',
118 | [ token ]
119 | );
120 | } catch ( error ) {
121 | console.error( error );
122 | throw error;
123 | }
124 |
125 | if ( res.rows.length === 0 ) {
126 | return false;
127 | } else {
128 | return true;
129 | }
130 | };
131 |
132 | /**
133 | * deletes all expired tokens
134 | */
135 | deleteExpiredTokens = async () => {
136 | let res;
137 | try {
138 | res = await this.provider.query(
139 | 'DELETE FROM tokens WHERE expire_date < (now() at time zone \'utc\')',
140 | []
141 | );
142 | } catch ( error ) {
143 | console.error( error );
144 | throw error;
145 | }
146 | return res.rows[ 0 ];
147 | };
148 | }
149 |
--------------------------------------------------------------------------------
/app/repository/two-factor.repository.ts:
--------------------------------------------------------------------------------
1 | import { PostgreSqlProvider } from '@open-template-hub/common';
2 |
3 | export class TwoFactorCodeRepository {
4 | constructor( private readonly provider: PostgreSqlProvider ) {
5 | }
6 |
7 | insertTwoFactorCode = async (
8 | username: string,
9 | phone_number: string,
10 | code: string,
11 | expiry: string
12 | ) => {
13 | try {
14 | await this.provider.query(
15 | 'INSERT INTO two_factor_auth_codes(username, phone_number, code, expiry) VALUES($1, $2, $3, $4)',
16 | [
17 | username,
18 | phone_number,
19 | code,
20 | expiry
21 | ]
22 | );
23 | } catch ( error ) {
24 | console.error( error );
25 | throw error;
26 | }
27 | };
28 |
29 | findTwoFactorCodeByUsername = async (
30 | username: string
31 | ) => {
32 | let res;
33 | try {
34 | res = await this.provider.query(
35 | 'SELECT * FROM two_factor_auth_codes WHERE username = $1 ORDER BY expiry DESC',
36 | [ username ]
37 | );
38 | } catch ( error ) {
39 | console.error( error );
40 | throw error;
41 | }
42 |
43 | return res.rows[ 0 ];
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/app/repository/user.repository.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds user repository
3 | */
4 |
5 | import { HttpError, PostgreSqlProvider, ResponseCode, User, } from '@open-template-hub/common';
6 |
7 | export class UserRepository {
8 | constructor( private readonly provider: PostgreSqlProvider ) {
9 | }
10 |
11 | /**
12 | * creates user
13 | * @param user user
14 | */
15 | insertUser = async ( user: User ) => {
16 | try {
17 | await this.provider.query(
18 | 'INSERT INTO users(username, password, role, email) VALUES($1, $2, $3, $4)',
19 | [ user.username, user.password, user.role, user.email ]
20 | );
21 | } catch ( error ) {
22 | console.error( error );
23 | throw error;
24 | }
25 | };
26 |
27 | /**
28 | * gets user by username or email
29 | * @param username username
30 | */
31 | findUserByUsernameOrEmail = async ( username: string, throwError: boolean = true ) => {
32 | let res;
33 | try {
34 | res = await this.provider.query(
35 | 'SELECT username, password, verified, role, phone_number, two_factor_enabled, email FROM users WHERE username = $1 or email = $1',
36 | [ username ]
37 | );
38 | if ( throwError ) {
39 | this.shouldHaveSingleRow( res );
40 | }
41 | } catch ( error ) {
42 | console.error( error );
43 | throw error;
44 | }
45 | return res.rows[ 0 ];
46 | };
47 |
48 | /**
49 | * gets email by username
50 | * @param username username
51 | */
52 | findEmailByUsername = async ( username: string ) => {
53 | let res;
54 | try {
55 | res = await this.provider.query(
56 | 'SELECT username, email FROM users WHERE username = $1',
57 | [ username ]
58 | );
59 | this.shouldHaveSingleRow( res );
60 | } catch ( error ) {
61 | console.error( error );
62 | throw error;
63 | }
64 | return res.rows[ 0 ];
65 | };
66 |
67 | /**
68 | * gets email and password by username
69 | * @param username username
70 | */
71 | findEmailAndPasswordByUsername = async ( username: string ) => {
72 | let res;
73 | try {
74 | res = await this.provider.query(
75 | 'SELECT username, email, password FROM users WHERE username = $1',
76 | [ username ]
77 | );
78 | } catch ( error ) {
79 | console.error( error );
80 | throw error;
81 | }
82 |
83 | if ( res.rows.length === 0 ) {
84 | let e = new Error( 'bad credentials' ) as HttpError;
85 | e.responseCode = ResponseCode.FORBIDDEN;
86 | throw e;
87 | } else if ( res.rows.length > 1 ) {
88 | console.error( 'ambiguous token' );
89 | let e = new Error( 'internal server error' ) as HttpError;
90 | e.responseCode = ResponseCode.INTERNAL_SERVER_ERROR;
91 | throw e;
92 | }
93 |
94 | return res.rows[ 0 ];
95 | };
96 |
97 | /**
98 | * checks user is verified or not
99 | * @param username username
100 | */
101 | verifyUser = async ( username: string ) => {
102 | try {
103 | const response = await this.provider.query(
104 | 'UPDATE users SET verified = true WHERE username = $1 RETURNING *',
105 | [ username ]
106 | );
107 |
108 | this.shouldHaveSingleRow(response);
109 | return response.rows[0];
110 | } catch ( error ) {
111 | console.error( error );
112 | throw error;
113 | }
114 | };
115 |
116 | addPhoneNumberToUser = async ( phoneNumber: string, username: string ) => {
117 | try {
118 | await this.provider.query(
119 | 'UPDATE users SET phone_number = $1 where username = $2',
120 | [ phoneNumber, username ]
121 | );
122 | } catch ( error ) {
123 | console.error( error );
124 | throw error;
125 | }
126 | };
127 |
128 | updateTwoFactorEnabled = async (
129 | isTwoFactorEnabled: boolean,
130 | username: string
131 | ) => {
132 | try {
133 | await this.provider.query(
134 | 'UPDATE users set two_factor_enabled = $1 where username = $2',
135 | [ isTwoFactorEnabled, username ]
136 | );
137 | } catch ( error ) {
138 | console.error( error );
139 | throw error;
140 | }
141 | };
142 |
143 | /**
144 | * updates user password by username
145 | * @param user user
146 | */
147 | updateByUsername = async ( user: User ) => {
148 | try {
149 | await this.provider.query(
150 | 'UPDATE users SET password = $1 WHERE username = $2',
151 | [ user.password, user.username ]
152 | );
153 | } catch ( error ) {
154 | console.error( error );
155 | throw error;
156 | }
157 | };
158 |
159 | /**
160 | * delete user by username
161 | * @param username username
162 | */
163 | deleteUserByUsername = async ( username: string ) => {
164 | try {
165 | await this.provider.query( 'DELETE FROM users WHERE username = $1', [
166 | username,
167 | ] );
168 | } catch ( error ) {
169 | console.error( error );
170 | throw error;
171 | }
172 | };
173 |
174 | /**
175 | * checks response has single row
176 | * @param res res
177 | */
178 | shouldHaveSingleRow = ( res: any ) => {
179 | if ( res.rows.length === 0 ) {
180 | let e = new Error( 'user not found' ) as HttpError;
181 | e.responseCode = ResponseCode.BAD_REQUEST;
182 | throw e;
183 | } else if ( res.rows.length > 1 ) {
184 | console.error( 'ambiguous token' );
185 | let e = new Error( 'internal server error' ) as HttpError;
186 | e.responseCode = ResponseCode.INTERNAL_SERVER_ERROR;
187 | throw e;
188 | }
189 | };
190 |
191 | findVerifiedPhoneNumberByUsername = async ( username: string ) => {
192 | let res;
193 | try {
194 | res = await this.provider.query(
195 | 'SELECT phone_number FROM users WHERE username = $1',
196 | [ username ]
197 | );
198 | } catch ( error ) {
199 | console.error( error );
200 | throw error;
201 | }
202 |
203 | return res.rows;
204 | };
205 |
206 | deletedSubmittedPhoneNumberByUsername = async ( username: string ) => {
207 | try {
208 | await this.provider.query(
209 | 'UPDATE users SET phone_number = null WHERE username = $1',
210 | [ username ]
211 | );
212 |
213 | await this.provider.query(
214 | 'UPDATE users SET two_factor_enabled = false WHERE username = $1',
215 | [ username ]
216 | );
217 | } catch ( error ) {
218 | console.error( error );
219 | throw error;
220 | }
221 | };
222 |
223 | getAllUsers = async ( role: string, verified: boolean | undefined, twoFA: boolean | undefined, oauth: string | undefined, username: string, offset: number, limit: number ) => {
224 | let response;
225 |
226 | try {
227 | let whereQueryString = 'WHERE role ILIKE $3 and (users.username LIKE $4 or email LIKE $5)';
228 | let whereQueryParams: Array = [ '%' + role + '%', '%' + username + '%', '%' + username + '%' ];
229 |
230 | let paramCounter = 6;
231 |
232 | if ( verified !== undefined ) {
233 | whereQueryString += ` and verified = $${ paramCounter }`;
234 | paramCounter += 1;
235 | whereQueryParams.push( verified );
236 | }
237 |
238 | if ( twoFA !== undefined ) {
239 | whereQueryString += ` and two_factor_enabled = $${ paramCounter }`;
240 | paramCounter += 1;
241 | whereQueryParams.push( twoFA );
242 | }
243 |
244 | if ( oauth !== undefined ) {
245 | if ( oauth === 'exclude' ) {
246 | whereQueryString += ` and social_login_mappings.social_login_key IS NULL`;
247 | } else {
248 | whereQueryString += ` and social_login_mappings.social_login_key = $${ paramCounter }`;
249 | whereQueryParams.push( oauth );
250 | }
251 | }
252 |
253 | response = await this.provider.query(
254 | `SELECT users.username, users.email, users.verified, users.phone_number as phoneNumber, users.two_factor_enabled as twoFactorEnabled, social_login_mappings.external_user_email, social_login_mappings.social_login_key FROM users LEFT JOIN social_login_mappings ON users.username = social_login_mappings.username ${ whereQueryString } ORDER BY users.username OFFSET $1 LIMIT $2`,
255 | [ offset, limit, ...whereQueryParams ]
256 | );
257 | } catch ( error ) {
258 | console.error( error );
259 | throw error;
260 | }
261 |
262 | return response.rows;
263 | };
264 |
265 | getAllUsersCount = async ( role: string, verified: boolean | undefined, twoFA: boolean | undefined, oauth: string | undefined, username: string ) => {
266 | let response;
267 | try {
268 |
269 | let whereQueryString = 'WHERE role ILIKE $1 and users.username LIKE $2';
270 | let whereQueryParams: any[] = [ '%' + role + '%', '%' + username + '%' ];
271 |
272 | let paramCounter = 3;
273 |
274 | if ( verified !== undefined ) {
275 | whereQueryString += ` and verified = $${ paramCounter }`;
276 | paramCounter += 1;
277 | whereQueryParams.push( verified );
278 | }
279 |
280 | if ( twoFA !== undefined ) {
281 | whereQueryString += ` and two_factor_enabled = $${ paramCounter }`;
282 | paramCounter += 1;
283 | whereQueryParams.push( twoFA );
284 | }
285 |
286 | if ( oauth !== undefined ) {
287 | if ( oauth === 'exclude' ) {
288 | whereQueryString += ` and social_login_mappings.social_login_key IS NULL`;
289 | } else {
290 | whereQueryString += ` and social_login_mappings.social_login_key = $${ paramCounter }`;
291 | whereQueryParams.push( oauth );
292 | }
293 | }
294 |
295 | response = await this.provider.query(
296 | `SELECT COUNT(*) FROM users LEFT JOIN social_login_mappings ON users.username = social_login_mappings.username ${ whereQueryString }`,
297 | [ ...whereQueryParams ]
298 | );
299 | this.shouldHaveSingleRow( response );
300 | } catch ( error ) {
301 | console.log( error );
302 | throw error;
303 | }
304 |
305 | return response.rows[ 0 ];
306 | };
307 | }
308 |
--------------------------------------------------------------------------------
/app/route/auth.route.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds auth routes
3 | */
4 |
5 | import { authorizedBy, ResponseCode, User, UserRole, } from '@open-template-hub/common';
6 | import { Request, Response } from 'express';
7 | import Router from 'express-promise-router';
8 | import { AuthController } from '../controller/auth.controller';
9 |
10 | const subRoutes = {
11 | root: '/',
12 | signup: '/signup',
13 | login: '/login',
14 | logout: '/logout',
15 | token: '/token',
16 | verify: '/verify',
17 | forgetPassword: '/forget-password',
18 | resetPassword: '/reset-password',
19 | resetPasswordToken: '/reset-password-token',
20 | user: '/user',
21 | submittedPhoneNumber: '/submitted-phone-number',
22 | users: '/users'
23 | };
24 |
25 | export const router = Router();
26 |
27 | router.post( subRoutes.signup, async ( req: Request, res: Response ) => {
28 | // sign up
29 | const authController = new AuthController();
30 | const context = res.locals.ctx;
31 | const response = await authController.signup(
32 | context.postgresql_provider,
33 | context.mongodb_provider,
34 | context.message_queue_provider,
35 | req.query.origin as string,
36 | {
37 | username: req.body.username,
38 | password: req.body.password,
39 | email: req.body.email,
40 | } as User,
41 | req.body.languageCode
42 | );
43 | res.status( ResponseCode.CREATED ).json( response );
44 | } );
45 |
46 | router.post( subRoutes.login, async ( req: Request, res: Response ) => {
47 | // login
48 | const authController = new AuthController();
49 | const context = res.locals.ctx;
50 | const response = await authController.login(
51 | context.postgresql_provider,
52 | context.mongodb_provider,
53 | context.message_queue_provider,
54 | req.query.origin as string,
55 | {
56 | username: req.body.username,
57 | password: req.body.password,
58 | email: req.body.email,
59 | } as User
60 | );
61 |
62 | res.status( ResponseCode.OK ).json( response );
63 | } );
64 |
65 | router.post( subRoutes.logout, async ( req: Request, res: Response ) => {
66 | // logout
67 | const authController = new AuthController();
68 | const context = res.locals.ctx;
69 | await authController.logout( context.postgresql_provider, req.body.token );
70 | res.status( ResponseCode.NO_CONTENT ).json( {} );
71 | } );
72 |
73 | router.post( subRoutes.token, async ( req: Request, res: Response ) => {
74 | // get token
75 | const authController = new AuthController();
76 | const context = res.locals.ctx;
77 | const accessToken = await authController.token(
78 | context.postgresql_provider,
79 | req.body.token
80 | );
81 | res
82 | .status( ResponseCode.OK )
83 | .json( { accessToken: accessToken, refreshToken: req.body.token } );
84 | } );
85 |
86 | router.get( subRoutes.verify, async ( req: Request, res: Response ) => {
87 | // verify token
88 | const authController = new AuthController();
89 | const context = res.locals.ctx;
90 | const verifyResponse = await authController.verify(
91 | context.postgresql_provider,
92 | req.query.token as string
93 | );
94 |
95 | res.status( ResponseCode.OK ).json( verifyResponse );
96 | } );
97 |
98 | router.post( subRoutes.forgetPassword, async ( req: Request, res: Response ) => {
99 | // forget password
100 | const authController = new AuthController();
101 | const context = res.locals.ctx;
102 | await authController.forgetPassword(
103 | context.postgresql_provider,
104 | context.message_queue_provider,
105 | req.query.origin as string,
106 | req.body.username,
107 | req.body.languageCode
108 | );
109 | res.status( ResponseCode.NO_CONTENT ).json( {} );
110 | } );
111 |
112 | router.get(
113 | subRoutes.resetPasswordToken,
114 | authorizedBy( [ UserRole.ADMIN ] ),
115 | async ( req: Request, res: Response ) => {
116 | // generate reset password token
117 | const authController = new AuthController();
118 | const context = res.locals.ctx;
119 | const resetPasswordToken = await authController.forgetPassword(
120 | context.postgresql_provider,
121 | context.message_queue_provider,
122 | req.query.origin as string,
123 | req.query.username as string,
124 | undefined,
125 | true
126 | );
127 | res.status( ResponseCode.OK ).json( { resetPasswordToken } );
128 | }
129 | );
130 |
131 | router.post( subRoutes.resetPassword, async ( req: Request, res: Response ) => {
132 | // reset password
133 | const authController = new AuthController();
134 | const context = res.locals.ctx;
135 | await authController.resetPassword(
136 | context.postgresql_provider,
137 | {
138 | username: req.body.username,
139 | password: req.body.password,
140 | } as User,
141 | req.body.token
142 | );
143 | res.status( ResponseCode.NO_CONTENT ).json( {} );
144 | } );
145 |
146 | router.delete(
147 | subRoutes.user,
148 | authorizedBy( [ UserRole.ADMIN ] ),
149 | async ( req: Request, res: Response ) => {
150 | // delete user
151 | const authController = new AuthController();
152 | const context = res.locals.ctx;
153 | await authController.deleteUser(
154 | context.postgresql_provider,
155 | context.username,
156 | {
157 | username: req.query.username,
158 | } as User
159 | );
160 | res.status( ResponseCode.NO_CONTENT ).json( {} );
161 | }
162 | );
163 |
164 | router.get(
165 | subRoutes.submittedPhoneNumber,
166 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
167 | async ( _req: Request, res: Response ) => {
168 | const authController = new AuthController();
169 | const context = res.locals.ctx;
170 | const submittedPhoneNumber = await authController.getSubmittedPhoneNumber(
171 | context.postgresql_provider,
172 | context.username
173 | );
174 | res.status( ResponseCode.OK ).json( { phoneNumber: submittedPhoneNumber } );
175 | }
176 | );
177 |
178 | router.delete(
179 | subRoutes.submittedPhoneNumber,
180 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
181 | async ( _req: Request, res: Response ) => {
182 | const authController = new AuthController();
183 | const context = res.locals.ctx;
184 | await authController.deleteSubmittedPhoneNumber(
185 | context.postgresql_provider,
186 | context.username
187 | );
188 | res.status( ResponseCode.OK ).json( {} );
189 | }
190 | );
191 |
192 | router.get(
193 | subRoutes.users,
194 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
195 | async ( req: Request, res: Response ) => {
196 | const authController = new AuthController();
197 | const context = res.locals.ctx;
198 |
199 | const users = await authController.getUsers(
200 | context.postgresql_provider,
201 | req.query.role as string,
202 | req.query.verified as string,
203 | req.query.oauth as string,
204 | req.query.twoFA as string,
205 | req.query.username as string,
206 | {
207 | offset: +( req.query.offset as string ),
208 | limit: +( req.query.limit as string )
209 | },
210 | );
211 |
212 | res.status( ResponseCode.OK ).json( users );
213 | }
214 | );
215 |
--------------------------------------------------------------------------------
/app/route/index.route.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds index routes
3 | */
4 |
5 | import {
6 | ContextArgs,
7 | mount as mountApp,
8 | MountArgs,
9 | MountAssets,
10 | Route,
11 | RouteArgs,
12 | } from '@open-template-hub/common';
13 | import { Environment } from '../../environment';
14 | import { AuthQueueConsumer } from '../consumer/auth-queue.consumer';
15 | import { router as authRouter } from './auth.route';
16 | import { router as infoRouter } from './info.route';
17 | import { router as monitorRouter } from './monitor.route';
18 | import { router as socialLoginRouter } from './social-login.route';
19 | import { router as twoFactorCodeRouter } from './two-factor-code.route';
20 |
21 | const subRoutes = {
22 | root: '/',
23 | monitor: '/monitor',
24 | auth: '/auth',
25 | social: '/social',
26 | info: '/info',
27 | twoFactorCode: '/2fa'
28 | };
29 |
30 | export namespace Routes {
31 | export function mount(app: any) {
32 | const envArgs = new Environment().args();
33 |
34 | const ctxArgs = {
35 | envArgs,
36 | providerAvailability: {
37 | mongo_enabled: false,
38 | postgre_enabled: true,
39 | mq_enabled: true,
40 | redis_enabled: false,
41 | },
42 | } as ContextArgs;
43 |
44 | const assets = {
45 | mqChannelTag: envArgs.mqArgs?.authServerMessageQueueChannel as string,
46 | queueConsumer: new AuthQueueConsumer(),
47 | applicationName: 'AuthServer',
48 | } as MountAssets;
49 |
50 | const routes: Array = [];
51 |
52 | routes.push( { name: subRoutes.auth, router: authRouter } );
53 | routes.push( { name: subRoutes.info, router: infoRouter } );
54 | routes.push( { name: subRoutes.monitor, router: monitorRouter } );
55 | routes.push( { name: subRoutes.social, router: socialLoginRouter } );
56 | routes.push( { name: subRoutes.twoFactorCode, router: twoFactorCodeRouter } );
57 |
58 | const routeArgs = { routes } as RouteArgs;
59 |
60 | const args = {
61 | app,
62 | ctxArgs,
63 | routeArgs,
64 | assets,
65 | } as MountArgs;
66 |
67 | mountApp(args);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/app/route/info.route.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds info routes
3 | */
4 |
5 | import { authorizedBy, ResponseCode, UserRole, } from '@open-template-hub/common';
6 | import { Request, Response } from 'express';
7 | import Router from 'express-promise-router';
8 | import { InfoController } from '../controller/info.controller';
9 |
10 | const subRoutes = {
11 | root: '/',
12 | me: '/me',
13 | other: '/other'
14 | };
15 |
16 | export const router = Router();
17 | const infoController = new InfoController();
18 |
19 | router.get(
20 | subRoutes.me,
21 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
22 | async ( _req: Request, res: Response ) => {
23 | // gets user info
24 | const context = res.locals.ctx;
25 | const response = await infoController.me(
26 | context.postgresql_provider,
27 | context.token
28 | );
29 | res.status( ResponseCode.OK ).json( response );
30 | }
31 | );
32 |
33 | router.get(
34 | subRoutes.other,
35 | authorizedBy( [ UserRole.ADMIN ] ),
36 | async ( req: Request, res: Response ) => {
37 | const context = res.locals.ctx;
38 | const response = await infoController.other(
39 | context.postgresql_provider,
40 | req.query.username as string
41 | );
42 | res.status( ResponseCode.OK ).json( response );
43 | }
44 | );
45 |
--------------------------------------------------------------------------------
/app/route/monitor.route.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds monitor routes
3 | */
4 |
5 | import { ResponseCode } from '@open-template-hub/common';
6 | import { Request, Response } from 'express';
7 | import Router from 'express-promise-router';
8 | import { version } from '../../version';
9 |
10 | const subRoutes = {
11 | root: '/',
12 | alive: '/alive',
13 | };
14 |
15 | export const router = Router();
16 |
17 | router.get( subRoutes.alive, async ( _req: Request, res: Response ) => {
18 | // check system is alive
19 | res.status( ResponseCode.OK ).json( { version } );
20 | } );
21 |
--------------------------------------------------------------------------------
/app/route/social-login.route.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds social login routes
3 | */
4 |
5 | import { ResponseCode } from '@open-template-hub/common';
6 | import { Request, Response } from 'express';
7 | import Router from 'express-promise-router';
8 | import { SocialLoginController } from '../controller/social-login.controller';
9 |
10 | const subRoutes = {
11 | root: '/',
12 | loginUrl: '/login-url',
13 | login: '/login',
14 | loginWithAccessToken: '/login-with-access-token',
15 | };
16 |
17 | export const publicRoutes = [
18 | subRoutes.loginUrl,
19 | subRoutes.login,
20 | subRoutes.loginWithAccessToken,
21 | ];
22 |
23 | export const router = Router();
24 |
25 | const socialLoginController = new SocialLoginController();
26 |
27 | router.post( subRoutes.loginUrl, async ( req: Request, res: Response ) => {
28 | // gets social login url
29 | const context = res.locals.ctx;
30 | const response = await socialLoginController.loginUrl(
31 | context.postgresql_provider,
32 | req.body
33 | );
34 | res.status( ResponseCode.OK ).json( { loginUrl: response } );
35 | } );
36 |
37 | router.post( subRoutes.login, async ( req: Request, res: Response ) => {
38 | // social login
39 | const context = res.locals.ctx;
40 | const response = await socialLoginController.login(
41 | context.postgresql_provider,
42 | context.message_queue_provider,
43 | req.body
44 | );
45 | res.status( ResponseCode.OK ).json( response );
46 | } );
47 |
48 | router.post(
49 | subRoutes.loginWithAccessToken,
50 | async ( req: Request, res: Response ) => {
51 | // login with access token
52 | const context = res.locals.ctx;
53 | const response = await socialLoginController.loginWithAccessToken(
54 | context.postgresql_provider,
55 | context.message_queue_provider,
56 | req.body
57 | );
58 | res.status( ResponseCode.OK ).json( response );
59 | }
60 | );
61 |
--------------------------------------------------------------------------------
/app/route/two-factor-code.route.ts:
--------------------------------------------------------------------------------
1 | import { authorizedBy, ResponseCode, UserRole, } from '@open-template-hub/common';
2 | import { Request, Response } from 'express';
3 | import Router from 'express-promise-router';
4 | import { TwoFactorCodeController } from '../controller/two-factor.controller';
5 | import { TwoFactorCode } from '../interface/two-factor-code.interface';
6 |
7 | const subRoutes = {
8 | root: '/',
9 | request: '/request',
10 | verify: '/verify',
11 | loginVerify: '/loginVerify',
12 | };
13 |
14 | export const router = Router();
15 |
16 | router.post(
17 | subRoutes.request,
18 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
19 | async ( req: Request, res: Response ) => {
20 | const twoFactorCodeController = new TwoFactorCodeController();
21 | const context = res.locals.ctx;
22 |
23 | const response = await twoFactorCodeController.request(
24 | context.postgresql_provider,
25 | context.message_queue_provider,
26 | {
27 | username: context.username,
28 | phoneNumber: req.body.phoneNumber,
29 | languageCode: req.body.languageCode,
30 | } as TwoFactorCode
31 | );
32 |
33 | res.status( ResponseCode.OK ).json( response );
34 | }
35 | );
36 |
37 | router.post(
38 | subRoutes.verify,
39 | authorizedBy( [ UserRole.ADMIN, UserRole.DEFAULT ] ),
40 | async ( req: Request, res: Response ) => {
41 | const twoFactorCodeController = new TwoFactorCodeController();
42 | const context = res.locals.ctx;
43 |
44 | await twoFactorCodeController.verify(
45 | context.postgresql_provider,
46 | context.username,
47 | req.body.code
48 | );
49 |
50 | res.status( ResponseCode.OK ).json( {} );
51 | }
52 | );
53 |
54 | router.post( subRoutes.loginVerify, async ( req: Request, res: Response ) => {
55 | const twoFactorCodeController = new TwoFactorCodeController();
56 | const context = res.locals.ctx;
57 |
58 | const loginVerifyResponse = await twoFactorCodeController.loginVerify(
59 | context.postgresql_provider,
60 | req.body.code,
61 | req.body.preAuthToken
62 | );
63 |
64 | return res.status( ResponseCode.OK ).json( loginVerifyResponse );
65 | } );
66 |
--------------------------------------------------------------------------------
/assets/requests/auth-requests.rest:
--------------------------------------------------------------------------------
1 | ### Sign Up
2 | POST http://localhost:4001/auth/signup
3 | Content-Type: application/json
4 |
5 | {
6 | "username": "username",
7 | "password": "password",
8 | "email": "email@email.com",
9 | "languageCode": "en"
10 | }
11 |
12 | ### Login
13 | POST http://localhost:4001/auth/login
14 | Content-Type: application/json
15 |
16 | {
17 | "username": "username",
18 | "password": "password"
19 | }
20 |
21 | ### Logout (Remove refresh token)
22 | POST http://localhost:4001/auth/logout
23 | Content-Type: application/json
24 |
25 | {
26 | "token": "token"
27 | }
28 |
29 | ### Refresh Token
30 | POST http://localhost:4001/auth/token
31 | Content-Type: application/json
32 |
33 | {
34 | "token": "token"
35 | }
36 |
37 | ### Verify Token
38 | GET http://localhost:4001/auth/verify?token=token
39 |
40 | ### Forget Password
41 | POST http://localhost:4001/auth/forget-password
42 | Content-Type: application/json
43 |
44 | {
45 | "username": "username",
46 | "languageCode": "en"
47 | }
48 |
49 | ### Reset Password
50 | POST http://localhost:4001/auth/reset-password
51 | Content-Type: application/json
52 |
53 | {
54 | "username": "username",
55 | "password": "password",
56 | "token": "token"
57 | }
58 |
59 | ### Delete User
60 | DELETE http://localhost:4001/auth/user?username=username
61 | Content-Type: application/json
62 | Authorization: Bearer token
63 |
64 | ### Get users
65 | GET http://localhost:4001/auth/users
66 | Content-Type: application/json
67 | Authorization: Bearer token
68 |
--------------------------------------------------------------------------------
/assets/requests/info-requests.rest:
--------------------------------------------------------------------------------
1 | ### Who am I
2 | GET http://localhost:4001/info/me
3 | Content-Type: application/json
4 | Authorization: Bearer token
5 |
6 | ### Get other user info (ADMIN)
7 | GET http://localhost:4001/info/other?username=mert
8 | Content-Type: application/json
9 | Authorization: Bearer token
10 |
--------------------------------------------------------------------------------
/assets/requests/monitor-requests.rest:
--------------------------------------------------------------------------------
1 | ### Is server alive
2 | GET http://localhost:4001/monitor/alive
3 | Content-Type: application/json
4 |
--------------------------------------------------------------------------------
/assets/requests/social-login-requests.rest:
--------------------------------------------------------------------------------
1 | ### Get Social Login URL
2 | POST http://localhost:4001/social/login-url
3 | Content-Type: application/json
4 |
5 | {
6 | "key": "GITHUB"
7 | }
8 |
9 | ### Social Login
10 | POST http://localhost:4001/social/login
11 | Content-Type: application/json
12 |
13 | {
14 | "key": "GITHUB",
15 | "code": "code",
16 | "state": "state"
17 | }
18 |
19 | ### Social Login With Access token
20 | POST http://localhost:4001/social/login-with-access-token
21 | Content-Type: application/json
22 |
23 | {
24 | "key": "GITHUB",
25 | "accessToken": "",
26 | "tokenType": "Bearer"
27 | }
28 |
--------------------------------------------------------------------------------
/assets/requests/team-requests.rest:
--------------------------------------------------------------------------------
1 | ### Team Create
2 | POST http://localhost:4001/team
3 | Content-Type: application/json
4 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnQiLCJyb2xlIjoiQURNSU4iLCJ0ZWFtcyI6W3siX2lkIjoiNjNiNGExOWExY2U1ODdmMzI3NGMyNTM0IiwidGVhbV9pZCI6IjYzYjRhMTlhMWNlNTg3ZjMyNzRjMjUzMyIsIm5hbWUiOiJPcGVuIFRlbXBsYXRlIEh1YiIsInBheWxvYWQiOnsid2Vic2l0ZSI6Im9wZW50ZW1wbGF0ZWh1Yi5jb20ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoicmVtb2tsaWZ5IiwiZW1haWwiOiJyZW1va2xpZnlAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6dHJ1ZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4MjI1ODg3MX19LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg0OTA0NDc3fX1dLCJyZWFkZXJzIjpbeyJ1c2VybmFtZSI6ImtlcmltYWxwIiwiZW1haWwiOiJrZXJpbWFscG1yQGdtYWlsLmNvbSIsImlzVmVyaWZpZWQiOnRydWUsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODM4Nzk2NDR9fSx7InVzZXJuYW1lIjoiZXl1cnRheSIsImVtYWlsIjoiZXl1cnRheXNAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6ZmFsc2UsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODQ5NzM4NzB9fSx7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NDk5Mzg2OX19XSwiX192IjowfSx7Il9pZCI6IjYzYjRhZDE5MWNlNTg3ZjMyNzRjMjVmOCIsInRlYW1faWQiOiI2M2I0YWQxOTFjZTU4N2YzMjc0YzI1ZjciLCJuYW1lIjoiUmVtb3RlIElubiIsInBheWxvYWQiOnsid2Vic2l0ZSI6ImxpbmtlZGluLmNvbS9jb21wYW55L3JlbW90ZS1pbm4ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTE5OTMyM319LHsidXNlcm5hbWUiOiJyZW1va2xpZnkiLCJlbWFpbCI6InJlbW9rbGlmeUBnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTIzMjAyM319LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg1MzUyNjIzfX1dLCJyZWFkZXJzIjpbXSwiX192IjowfV0sImlhdCI6MTY3MzEyNTAxMCwiZXhwIjoxNjczMTI4NjEwfQ.-j9J7mQ_mz0O5mAlbrPN0lEG0via37LvDls2XUQIpSQ
5 |
6 | {
7 | "name": "Test Team",
8 | "payload": {
9 | "website": "https://opentemplatehub.com"
10 | }
11 | }
12 |
13 | ### Verify team
14 | POST http://localhost:4001/team/verify
15 | Content-Type: application/json
16 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnQyIiwicm9sZSI6IkRFRkFVTFQiLCJ0ZWFtcyI6W3siX2lkIjoiNjNiOWUwOGMwMDg1MTJmYzRkOTQ4ZTRhIiwidGVhbV9pZCI6IjYzYjllMDhjMDA4NTEyZmM0ZDk0OGU0OSIsIm5hbWUiOiJUZXN0IFRlYW0iLCJwYXlsb2FkIjp7IndlYnNpdGUiOiJodHRwczovL29wZW50ZW1wbGF0ZWh1Yi5jb20ifSwiY3JlYXRvciI6Im1lcnQiLCJ3cml0ZXJzIjpbXSwicmVhZGVycyI6W3sidXNlcm5hbWUiOiJtZXJ0MiIsImVtYWlsIjoibWVydGxzYXJhY0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3MzEzNTg0NjEwMn19XSwiX192IjowfV0sImlhdCI6MTY3MzEzNjY1MiwiZXhwIjoxNjczMTQwMjUyfQ.-PKaXu-O35mPIKSunUS-fZxQ-mQLk-2rgpjOGdIi2AQ
17 |
18 | {
19 | "verifyToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnRsc2FyYWNAZ21haWwuY29tIiwidGVhbSI6eyJpZCI6IjYzYjllMDhjMDA4NTEyZmM0ZDk0OGU0OSIsInJvbGUiOiJyZWFkZXJzIn0sImlhdCI6MTY3MzEzNTg0NiwiZXhwIjoxNjczOTk5ODQ2fQ.ZsGwB_B74AC2nzk7MQxYdtBvywOOZGP3DaW6di2q440"
20 | }
21 |
22 | ### Add Writer
23 | POST http://localhost:4001/team/writer
24 | Content-Type: application/json
25 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnQiLCJyb2xlIjoiQURNSU4iLCJ0ZWFtcyI6W3siX2lkIjoiNjNiNGExOWExY2U1ODdmMzI3NGMyNTM0IiwidGVhbV9pZCI6IjYzYjRhMTlhMWNlNTg3ZjMyNzRjMjUzMyIsIm5hbWUiOiJPcGVuIFRlbXBsYXRlIEh1YiIsInBheWxvYWQiOnsid2Vic2l0ZSI6Im9wZW50ZW1wbGF0ZWh1Yi5jb20ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoicmVtb2tsaWZ5IiwiZW1haWwiOiJyZW1va2xpZnlAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6dHJ1ZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4MjI1ODg3MX19LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg0OTA0NDc3fX1dLCJyZWFkZXJzIjpbeyJ1c2VybmFtZSI6ImtlcmltYWxwIiwiZW1haWwiOiJrZXJpbWFscG1yQGdtYWlsLmNvbSIsImlzVmVyaWZpZWQiOnRydWUsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODM4Nzk2NDR9fSx7InVzZXJuYW1lIjoiZXl1cnRheSIsImVtYWlsIjoiZXl1cnRheXNAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6ZmFsc2UsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODQ5NzM4NzB9fSx7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NDk5Mzg2OX19XSwiX192IjowfSx7Il9pZCI6IjYzYjRhZDE5MWNlNTg3ZjMyNzRjMjVmOCIsInRlYW1faWQiOiI2M2I0YWQxOTFjZTU4N2YzMjc0YzI1ZjciLCJuYW1lIjoiUmVtb3RlIElubiIsInBheWxvYWQiOnsid2Vic2l0ZSI6ImxpbmtlZGluLmNvbS9jb21wYW55L3JlbW90ZS1pbm4ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTE5OTMyM319LHsidXNlcm5hbWUiOiJyZW1va2xpZnkiLCJlbWFpbCI6InJlbW9rbGlmeUBnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTIzMjAyM319LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg1MzUyNjIzfX1dLCJyZWFkZXJzIjpbXSwiX192IjowfSx7Il9pZCI6IjYzYjllMDhjMDA4NTEyZmM0ZDk0OGU0YSIsInRlYW1faWQiOiI2M2I5ZTA4YzAwODUxMmZjNGQ5NDhlNDkiLCJuYW1lIjoiVGVzdCBUZWFtIiwicGF5bG9hZCI6eyJ3ZWJzaXRlIjoiaHR0cHM6Ly9vcGVudGVtcGxhdGVodWIuY29tIn0sImNyZWF0b3IiOiJtZXJ0Iiwid3JpdGVycyI6W10sInJlYWRlcnMiOltdLCJfX3YiOjB9XSwiaWF0IjoxNjczMTI5OTY2LCJleHAiOjE2NzMxMzM1NjZ9.WxwQnDEMeZA8Dety06P8A3YDbWO4wQpg5pdUGWUMppQ
26 |
27 | {
28 | "teamId": "63b9e08c008512fc4d948e49",
29 | "writerUsername": "mert2",
30 | "origin": "https://localhost:4000"
31 | }
32 |
33 | ### Add Reader
34 | POST http://localhost:4001/team/reader
35 | Content-Type: application/json
36 | Origin: http://localhost:4000.com
37 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnQiLCJyb2xlIjoiQURNSU4iLCJ0ZWFtcyI6W3siX2lkIjoiNjNiNGExOWExY2U1ODdmMzI3NGMyNTM0IiwidGVhbV9pZCI6IjYzYjRhMTlhMWNlNTg3ZjMyNzRjMjUzMyIsIm5hbWUiOiJPcGVuIFRlbXBsYXRlIEh1YiIsInBheWxvYWQiOnsid2Vic2l0ZSI6Im9wZW50ZW1wbGF0ZWh1Yi5jb20ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoicmVtb2tsaWZ5IiwiZW1haWwiOiJyZW1va2xpZnlAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6dHJ1ZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4MjI1ODg3MX19LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg0OTA0NDc3fX1dLCJyZWFkZXJzIjpbeyJ1c2VybmFtZSI6ImtlcmltYWxwIiwiZW1haWwiOiJrZXJpbWFscG1yQGdtYWlsLmNvbSIsImlzVmVyaWZpZWQiOnRydWUsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODM4Nzk2NDR9fSx7InVzZXJuYW1lIjoiZXl1cnRheSIsImVtYWlsIjoiZXl1cnRheXNAZ21haWwuY29tIiwiaXNWZXJpZmllZCI6ZmFsc2UsInBheWxvYWQiOnsiaW52aXRhdGlvbkRhdGUiOjE2NzI3ODQ5NzM4NzB9fSx7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NDk5Mzg2OX19XSwiX192IjowfSx7Il9pZCI6IjYzYjRhZDE5MWNlNTg3ZjMyNzRjMjVmOCIsInRlYW1faWQiOiI2M2I0YWQxOTFjZTU4N2YzMjc0YzI1ZjciLCJuYW1lIjoiUmVtb3RlIElubiIsInBheWxvYWQiOnsid2Vic2l0ZSI6ImxpbmtlZGluLmNvbS9jb21wYW55L3JlbW90ZS1pbm4ifSwiY3JlYXRvciI6ImZ1cmthbiIsIndyaXRlcnMiOlt7InVzZXJuYW1lIjoidWxhcyIsImVtYWlsIjoidWxhc2FyZGFidXJha0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTE5OTMyM319LHsidXNlcm5hbWUiOiJyZW1va2xpZnkiLCJlbWFpbCI6InJlbW9rbGlmeUBnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjpmYWxzZSwicGF5bG9hZCI6eyJpbnZpdGF0aW9uRGF0ZSI6MTY3Mjc4NTIzMjAyM319LHsidXNlcm5hbWUiOiJtZXJ0IiwiZW1haWwiOiJtZXJ0c2FyYWNAeWFuZGV4LmNvbSIsImlzVmVyaWZpZWQiOmZhbHNlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjcyNzg1MzUyNjIzfX1dLCJyZWFkZXJzIjpbXSwiX192IjowfSx7Il9pZCI6IjYzYjllMDhjMDA4NTEyZmM0ZDk0OGU0YSIsInRlYW1faWQiOiI2M2I5ZTA4YzAwODUxMmZjNGQ5NDhlNDkiLCJuYW1lIjoiVGVzdCBUZWFtIiwicGF5bG9hZCI6eyJ3ZWJzaXRlIjoiaHR0cHM6Ly9vcGVudGVtcGxhdGVodWIuY29tIn0sImNyZWF0b3IiOiJtZXJ0Iiwid3JpdGVycyI6W10sInJlYWRlcnMiOltdLCJfX3YiOjB9XSwiaWF0IjoxNjczMTM0MTUxLCJleHAiOjE2NzMxMzc3NTF9.G7KzTY0nghxH0bFB3UlRrj0m4VuHTXIgjVU9SldlstU
38 |
39 | {
40 | "teamId": "63b9e08c008512fc4d948e49",
41 | "readerEmail": "mertlsarac@gmail.com"
42 | }
43 |
44 | ### Get Teams
45 | GET http://localhost:4001/team?teamId=63b9e08c008512fc4d948e49
46 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1lcnQyIiwicm9sZSI6IkRFRkFVTFQiLCJ0ZWFtcyI6W3siX2lkIjoiNjNiOWUwOGMwMDg1MTJmYzRkOTQ4ZTRhIiwidGVhbV9pZCI6IjYzYjllMDhjMDA4NTEyZmM0ZDk0OGU0OSIsIm5hbWUiOiJUZXN0IFRlYW0iLCJwYXlsb2FkIjp7IndlYnNpdGUiOiJodHRwczovL29wZW50ZW1wbGF0ZWh1Yi5jb20ifSwiY3JlYXRvciI6Im1lcnQiLCJ3cml0ZXJzIjpbXSwicmVhZGVycyI6W3sidXNlcm5hbWUiOiJtZXJ0MiIsImVtYWlsIjoibWVydGxzYXJhY0BnbWFpbC5jb20iLCJpc1ZlcmlmaWVkIjp0cnVlLCJwYXlsb2FkIjp7Imludml0YXRpb25EYXRlIjoxNjczMTM1ODQ2MTAyfX1dLCJfX3YiOjB9XSwiaWF0IjoxNjczMTg1MDUyLCJleHAiOjE2NzMxODg2NTJ9.APH9FYlNQy0tgtrPryLcVM0fEBGc5RKQlSAKQuK7U80
--------------------------------------------------------------------------------
/assets/requests/two-factor-requests.rest:
--------------------------------------------------------------------------------
1 | ### Request
2 | POST http://localhost:4001/2fa/request
3 | Content-Type: application/json
4 | Authorization: Bearer token
5 |
6 | {
7 | "phoneNumber": "+1111111",
8 | "languageCode": "en"
9 | }
10 |
11 | ### Verify
12 | POST http://localhost:4001/2fa/verify
13 | Content-Type: application/json
14 | Authorization: Bearer token
15 |
16 | {
17 | "code": "95680"
18 | }
19 |
--------------------------------------------------------------------------------
/assets/sql/preload.tables.psql:
--------------------------------------------------------------------------------
1 | SELECT pg_terminate_backend(pid)
2 | FROM pg_stat_activity
3 | WHERE application_name ILIKE ('AuthServer%') AND state NOT LIKE 'active' AND pid <> pg_backend_pid();
4 |
5 | create TABLE IF NOT EXISTS users
6 | (
7 | username text NOT NULL PRIMARY KEY,
8 | password text NOT NULL,
9 | email text UNIQUE,
10 | verified boolean DEFAULT FALSE,
11 | role text,
12 | phone_number text,
13 | two_factor_enabled boolean DEFAULT FALSE
14 | );
15 |
16 | create TABLE IF NOT EXISTS tokens
17 | (
18 | token text NOT NULL,
19 | expire_date date
20 | );
21 |
22 | create TABLE IF NOT EXISTS social_logins
23 | (
24 | social_login_key text NOT NULL UNIQUE,
25 | developer_notes text
26 | );
27 |
28 | create TABLE IF NOT EXISTS oauth_v1_config_params
29 | (
30 | social_login_key text NOT NULL UNIQUE,
31 | client_id text NOT NULL,
32 | client_secret text NOT NULL,
33 | redirect_uri text NOT NULL,
34 | login_uri text NOT NULL,
35 | access_token_uri text NOT NULL,
36 | access_token_query_param_field_path text NOT NULL,
37 | user_data_uri text NOT NULL,
38 | external_user_id_query_param_field_path text NOT NULL,
39 | external_user_email_query_param_field_path text,
40 | external_username_query_param_field_path text,
41 | request_token_uri text NOT NULL,
42 | access_token_request_method text NOT NULL
43 | );
44 |
45 | create TABLE IF NOT EXISTS oauth_v2_config_params
46 | (
47 | social_login_key text NOT NULL UNIQUE,
48 | client_id text NOT NULL,
49 | client_secret text NOT NULL,
50 | redirect_uri text NOT NULL,
51 | login_uri text NOT NULL,
52 | access_token_uri text NOT NULL,
53 | access_token_json_field_path text NOT NULL,
54 | user_data_uri text NOT NULL,
55 | external_user_id_json_field_path text NOT NULL,
56 | external_user_email_json_field_path text,
57 | external_username_json_field_path text,
58 | token_type_json_field_path text,
59 | requested_with_auth_header boolean NOT NULL,
60 | access_token_request_method text NOT NULL
61 | );
62 |
63 | create TABLE IF NOT EXISTS social_login_mappings
64 | (
65 | social_login_key text NOT NULL,
66 | username text NOT NULL,
67 | external_user_id text NOT NULL,
68 | external_username text,
69 | external_user_email text
70 | );
71 |
72 | create TABLE IF NOT EXISTS two_factor_auth_codes
73 | (
74 | username text NOT NULL,
75 | phone_number text NOT NULL,
76 | code text NOT NULL,
77 | expiry text NOT NULL
78 | );
79 |
--------------------------------------------------------------------------------
/assets/test-results/postman-dark.html:
--------------------------------------------------------------------------------
1 | Using htmlextra version 1.20.1
2 | Created the htmlextra report.
3 |
--------------------------------------------------------------------------------
/assets/tests/regression-tests/postman/README.md:
--------------------------------------------------------------------------------
1 | # POSTMAN REGRESSION TESTS
2 |
3 | ## Prerequisite
4 |
5 | Please check README.md to get more information to configure environment
6 |
7 | ## Configuration
8 |
9 | * Import **auth-server-regression.postman_environment.json** as environment to Postman
10 |
11 | * Import **auth-server-regression.postman_collection.json** as collection to Postman
12 |
13 | * Update imported environment variables and run collection on Postman
14 |
--------------------------------------------------------------------------------
/assets/tests/regression-tests/postman/auth-server-regression.postman_collection.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "_postman_id": "54e9576b-ca26-407e-b195-2c4b6e95b36f",
4 | "name": "auth-server-regression",
5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6 | },
7 | "item": [
8 | {
9 | "name": "Auth",
10 | "item": [
11 | {
12 | "name": "Sign Up",
13 | "item": [
14 | {
15 | "name": "Sign Up Success [201]",
16 | "event": [
17 | {
18 | "listen": "test",
19 | "script": {
20 | "exec": [
21 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
22 | "const encrypted = pm.response.json();\r",
23 | "const decrypted = CryptoJS.RC4.decrypt(\r",
24 | " encrypted,\r",
25 | " responseEncryptionSecret\r",
26 | " ).toString(CryptoJS.enc.Utf8);\r",
27 | "\r",
28 | "const decryptedJson = JSON.parse(decrypted);\r",
29 | "console.log(decryptedJson);\r",
30 | "\r",
31 | "pm.test(\"Response code is CREATED\", function () {\r",
32 | " pm.response.to.have.status(201);\r",
33 | "});\r",
34 | "\r",
35 | "var autoVerifyEnabled = pm.environment.get(\"auto-verify-enabled\");\r",
36 | "\r",
37 | "if (autoVerifyEnabled) {\r",
38 | " pm.test(\"Response contains Access Token\", function () {\r",
39 | " pm.expect(decrypted).to.include('accessToken');\r",
40 | " pm.environment.set(\"access-token\", decryptedJson.accessToken);\r",
41 | " });\r",
42 | "\r",
43 | " pm.test(\"Response contains Refresh Token\", function () {\r",
44 | " pm.expect(decrypted).to.include('refreshToken');\r",
45 | " pm.environment.set(\"refresh-token\", decryptedJson.refreshToken);\r",
46 | " });\r",
47 | "} else {\r",
48 | " pm.test(\"Response contains Created User Email\", function () {\r",
49 | " pm.expect(decrypted).to.include('email');\r",
50 | " });\r",
51 | "}\r",
52 | ""
53 | ],
54 | "type": "text/javascript"
55 | }
56 | },
57 | {
58 | "listen": "prerequest",
59 | "script": {
60 | "exec": [
61 | ""
62 | ],
63 | "type": "text/javascript"
64 | }
65 | }
66 | ],
67 | "request": {
68 | "method": "POST",
69 | "header": [],
70 | "body": {
71 | "mode": "raw",
72 | "raw": "{\r\n \"username\": \"{{username}}\",\r\n \"password\": \"{{password}}\",\r\n \"email\": \"{{email}}\"\r\n}",
73 | "options": {
74 | "raw": {
75 | "language": "json"
76 | }
77 | }
78 | },
79 | "url": {
80 | "raw": "{{uri}}/auth/signup",
81 | "host": [
82 | "{{uri}}"
83 | ],
84 | "path": [
85 | "auth",
86 | "signup"
87 | ]
88 | }
89 | },
90 | "response": []
91 | }
92 | ]
93 | },
94 | {
95 | "name": "Sign In",
96 | "item": [
97 | {
98 | "name": "Sign In By Username Success [200]",
99 | "event": [
100 | {
101 | "listen": "test",
102 | "script": {
103 | "exec": [
104 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
105 | "const encrypted = pm.response.json();\r",
106 | "const decrypted = CryptoJS.RC4.decrypt(\r",
107 | " encrypted,\r",
108 | " responseEncryptionSecret\r",
109 | " ).toString(CryptoJS.enc.Utf8);\r",
110 | "\r",
111 | "const decryptedJson = JSON.parse(decrypted);\r",
112 | "console.log(decryptedJson);\r",
113 | "\r",
114 | "var autoVerifyEnabled = pm.environment.get(\"auto-verify-enabled\");\r",
115 | "\r",
116 | "if (autoVerifyEnabled) {\r",
117 | " pm.test(\"Response code is OK\", function () {\r",
118 | " pm.response.to.have.status(200);\r",
119 | " });\r",
120 | "\r",
121 | " pm.test(\"Response contains Access Token\", function () {\r",
122 | " pm.expect(decrypted).to.include('accessToken');\r",
123 | " pm.environment.set(\"access-token\", decryptedJson.accessToken);\r",
124 | " });\r",
125 | "\r",
126 | " pm.test(\"Response contains Refresh Token\", function () {\r",
127 | " pm.expect(decrypted).to.include('refreshToken');\r",
128 | " pm.environment.set(\"refresh-token\", decryptedJson.refreshToken);\r",
129 | " });\r",
130 | "} else {\r",
131 | " pm.test(\"Response code is OK\", function () {\r",
132 | " pm.response.to.have.status(403);\r",
133 | " });\r",
134 | "\r",
135 | " pm.test(\"Response contains Error Message\", function () {\r",
136 | " pm.expect(decrypted).to.include('message');\r",
137 | " });\r",
138 | "}"
139 | ],
140 | "type": "text/javascript"
141 | }
142 | }
143 | ],
144 | "request": {
145 | "method": "POST",
146 | "header": [],
147 | "body": {
148 | "mode": "raw",
149 | "raw": "{\r\n \"username\": \"{{username}}\",\r\n \"password\": \"{{password}}\"\r\n}",
150 | "options": {
151 | "raw": {
152 | "language": "json"
153 | }
154 | }
155 | },
156 | "url": {
157 | "raw": "{{uri}}/auth/login",
158 | "host": [
159 | "{{uri}}"
160 | ],
161 | "path": [
162 | "auth",
163 | "login"
164 | ]
165 | }
166 | },
167 | "response": []
168 | },
169 | {
170 | "name": "Sign In By Email Success [200]",
171 | "event": [
172 | {
173 | "listen": "test",
174 | "script": {
175 | "exec": [
176 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
177 | "const encrypted = pm.response.json();\r",
178 | "const decrypted = CryptoJS.RC4.decrypt(\r",
179 | " encrypted,\r",
180 | " responseEncryptionSecret\r",
181 | " ).toString(CryptoJS.enc.Utf8);\r",
182 | "\r",
183 | "const decryptedJson = JSON.parse(decrypted);\r",
184 | "console.log(decryptedJson);\r",
185 | "\r",
186 | "var autoVerifyEnabled = pm.environment.get(\"auto-verify-enabled\");\r",
187 | "\r",
188 | "if (autoVerifyEnabled) {\r",
189 | " pm.test(\"Response code is OK\", function () {\r",
190 | " pm.response.to.have.status(200);\r",
191 | " });\r",
192 | "\r",
193 | " pm.test(\"Response contains Access Token\", function () {\r",
194 | " pm.expect(decrypted).to.include('accessToken');\r",
195 | " pm.environment.set(\"access-token\", decryptedJson.accessToken);\r",
196 | " });\r",
197 | "\r",
198 | " pm.test(\"Response contains Refresh Token\", function () {\r",
199 | " pm.expect(decrypted).to.include('refreshToken');\r",
200 | " pm.environment.set(\"refresh-token\", decryptedJson.refreshToken);\r",
201 | " });\r",
202 | "} else {\r",
203 | " pm.test(\"Response code is OK\", function () {\r",
204 | " pm.response.to.have.status(403);\r",
205 | " });\r",
206 | "\r",
207 | " pm.test(\"Response contains Error Message\", function () {\r",
208 | " pm.expect(decrypted).to.include('message');\r",
209 | " });\r",
210 | "}"
211 | ],
212 | "type": "text/javascript"
213 | }
214 | }
215 | ],
216 | "request": {
217 | "method": "POST",
218 | "header": [],
219 | "body": {
220 | "mode": "raw",
221 | "raw": "{\r\n \"email\": \"{{email}}\",\r\n \"password\": \"{{password}}\"\r\n}",
222 | "options": {
223 | "raw": {
224 | "language": "json"
225 | }
226 | }
227 | },
228 | "url": {
229 | "raw": "{{uri}}/auth/login",
230 | "host": [
231 | "{{uri}}"
232 | ],
233 | "path": [
234 | "auth",
235 | "login"
236 | ]
237 | }
238 | },
239 | "response": []
240 | },
241 | {
242 | "name": "Sign In Failure [400]",
243 | "event": [
244 | {
245 | "listen": "test",
246 | "script": {
247 | "exec": [
248 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
249 | "const encrypted = pm.response.json();\r",
250 | "const decrypted = CryptoJS.RC4.decrypt(\r",
251 | " encrypted,\r",
252 | " responseEncryptionSecret\r",
253 | " ).toString(CryptoJS.enc.Utf8);\r",
254 | "\r",
255 | "const decryptedJson = JSON.parse(decrypted);\r",
256 | "console.log(decryptedJson);\r",
257 | "\r",
258 | "pm.test(\"Response code is Bad Request\", function () {\r",
259 | " pm.response.to.have.status(400);\r",
260 | "});\r",
261 | "\r",
262 | "pm.test(\"Response contains Error Message\", function () {\r",
263 | " pm.expect(decrypted).to.include('message');\r",
264 | "});"
265 | ],
266 | "type": "text/javascript"
267 | }
268 | }
269 | ],
270 | "request": {
271 | "method": "POST",
272 | "header": [],
273 | "body": {
274 | "mode": "raw",
275 | "raw": "{\r\n \"password\": \"{{password}}\"\r\n}",
276 | "options": {
277 | "raw": {
278 | "language": "json"
279 | }
280 | }
281 | },
282 | "url": {
283 | "raw": "{{uri}}/auth/login",
284 | "host": [
285 | "{{uri}}"
286 | ],
287 | "path": [
288 | "auth",
289 | "login"
290 | ]
291 | }
292 | },
293 | "response": []
294 | },
295 | {
296 | "name": "Request New Token Success [200]",
297 | "event": [
298 | {
299 | "listen": "test",
300 | "script": {
301 | "exec": [
302 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
303 | "const encrypted = pm.response.json();\r",
304 | "const decrypted = CryptoJS.RC4.decrypt(\r",
305 | " encrypted,\r",
306 | " responseEncryptionSecret\r",
307 | " ).toString(CryptoJS.enc.Utf8);\r",
308 | "\r",
309 | "const decryptedJson = JSON.parse(decrypted);\r",
310 | "console.log(decryptedJson);\r",
311 | "\r",
312 | "var autoVerifyEnabled = pm.environment.get(\"auto-verify-enabled\");\r",
313 | "\r",
314 | "if (autoVerifyEnabled) {\r",
315 | " pm.test(\"Response code is OK\", function () {\r",
316 | " pm.response.to.have.status(200);\r",
317 | " });\r",
318 | "\r",
319 | " pm.test(\"Response contains Access Token\", function () {\r",
320 | " pm.expect(decrypted).to.include('accessToken');\r",
321 | " pm.environment.set(\"access-token\", decryptedJson.accessToken);\r",
322 | " });\r",
323 | "\r",
324 | " pm.test(\"Response contains Refresh Token\", function () {\r",
325 | " pm.expect(decrypted).to.include('refreshToken');\r",
326 | " pm.environment.set(\"refresh-token\", decryptedJson.refreshToken);\r",
327 | " });\r",
328 | "} else {\r",
329 | " pm.test(\"Response code is OK\", function () {\r",
330 | " pm.response.to.have.status(401);\r",
331 | " });\r",
332 | "\r",
333 | " pm.test(\"Response contains Error Message\", function () {\r",
334 | " pm.expect(decrypted).to.include('message');\r",
335 | " });\r",
336 | "}"
337 | ],
338 | "type": "text/javascript"
339 | }
340 | }
341 | ],
342 | "request": {
343 | "method": "POST",
344 | "header": [],
345 | "body": {
346 | "mode": "raw",
347 | "raw": "{\r\n \"token\": \"{{refresh-token}}\"\r\n}",
348 | "options": {
349 | "raw": {
350 | "language": "json"
351 | }
352 | }
353 | },
354 | "url": {
355 | "raw": "{{uri}}/auth/token",
356 | "host": [
357 | "{{uri}}"
358 | ],
359 | "path": [
360 | "auth",
361 | "token"
362 | ]
363 | }
364 | },
365 | "response": []
366 | }
367 | ]
368 | },
369 | {
370 | "name": "Password",
371 | "item": [
372 | {
373 | "name": "Forget Password Success [200]",
374 | "event": [
375 | {
376 | "listen": "test",
377 | "script": {
378 | "exec": [
379 | "pm.test(\"Response code is OK\", function () {\r",
380 | " pm.response.to.have.status(204);\r",
381 | "});\r",
382 | ""
383 | ],
384 | "type": "text/javascript"
385 | }
386 | }
387 | ],
388 | "request": {
389 | "method": "POST",
390 | "header": [],
391 | "body": {
392 | "mode": "raw",
393 | "raw": "{\r\n \"username\": \"{{username}}\"\r\n}",
394 | "options": {
395 | "raw": {
396 | "language": "json"
397 | }
398 | }
399 | },
400 | "url": {
401 | "raw": "{{uri}}/auth/forget-password",
402 | "host": [
403 | "{{uri}}"
404 | ],
405 | "path": [
406 | "auth",
407 | "forget-password"
408 | ]
409 | }
410 | },
411 | "response": []
412 | },
413 | {
414 | "name": "Get Reset Password Token Success [200]",
415 | "event": [
416 | {
417 | "listen": "test",
418 | "script": {
419 | "exec": [
420 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
421 | "const encrypted = pm.response.json();\r",
422 | "const decrypted = CryptoJS.RC4.decrypt(\r",
423 | " encrypted,\r",
424 | " responseEncryptionSecret\r",
425 | " ).toString(CryptoJS.enc.Utf8);\r",
426 | "\r",
427 | "const decryptedJson = JSON.parse(decrypted);\r",
428 | "console.log(decryptedJson);\r",
429 | "\r",
430 | "pm.test(\"Response code is OK\", function () {\r",
431 | " pm.response.to.have.status(200);\r",
432 | "});\r",
433 | "\r",
434 | "pm.test(\"Response contains Reset Password Token\", function () {\r",
435 | " pm.expect(decrypted).to.include('resetPasswordToken');\r",
436 | " pm.environment.set(\"reset-password-token\", decryptedJson.resetPasswordToken);\r",
437 | "});"
438 | ],
439 | "type": "text/javascript"
440 | }
441 | }
442 | ],
443 | "request": {
444 | "auth": {
445 | "type": "bearer",
446 | "bearer": [
447 | {
448 | "key": "token",
449 | "value": "{{admin-auth-token}}",
450 | "type": "string"
451 | }
452 | ]
453 | },
454 | "method": "GET",
455 | "header": [],
456 | "url": {
457 | "raw": "{{uri}}/auth/reset-password-token?username={{username}}",
458 | "host": [
459 | "{{uri}}"
460 | ],
461 | "path": [
462 | "auth",
463 | "reset-password-token"
464 | ],
465 | "query": [
466 | {
467 | "key": "username",
468 | "value": "{{username}}"
469 | }
470 | ]
471 | }
472 | },
473 | "response": []
474 | },
475 | {
476 | "name": "Get Reset Password Token Failure [403]",
477 | "event": [
478 | {
479 | "listen": "test",
480 | "script": {
481 | "exec": [
482 | "pm.test(\"Response code is Forbidden\", function () {\r",
483 | " pm.response.to.have.status(403);\r",
484 | "});"
485 | ],
486 | "type": "text/javascript"
487 | }
488 | }
489 | ],
490 | "request": {
491 | "auth": {
492 | "type": "bearer",
493 | "bearer": [
494 | {
495 | "key": "token",
496 | "value": "{{access-token}}",
497 | "type": "string"
498 | }
499 | ]
500 | },
501 | "method": "GET",
502 | "header": [],
503 | "url": {
504 | "raw": "{{uri}}/auth/reset-password-token?username={{username}}",
505 | "host": [
506 | "{{uri}}"
507 | ],
508 | "path": [
509 | "auth",
510 | "reset-password-token"
511 | ],
512 | "query": [
513 | {
514 | "key": "username",
515 | "value": "{{username}}"
516 | }
517 | ]
518 | }
519 | },
520 | "response": []
521 | },
522 | {
523 | "name": "Reset Password Success [200]",
524 | "event": [
525 | {
526 | "listen": "test",
527 | "script": {
528 | "exec": [
529 | "pm.test(\"Response code is OK\", function () {\r",
530 | " pm.response.to.have.status(204);\r",
531 | "});\r",
532 | ""
533 | ],
534 | "type": "text/javascript"
535 | }
536 | }
537 | ],
538 | "request": {
539 | "method": "POST",
540 | "header": [],
541 | "body": {
542 | "mode": "raw",
543 | "raw": "{\r\n \"username\": \"{{username}}\",\r\n \"password\": \"{{changed-password}}\",\r\n \"token\": \"{{reset-password-token}}\"\r\n}",
544 | "options": {
545 | "raw": {
546 | "language": "json"
547 | }
548 | }
549 | },
550 | "url": {
551 | "raw": "{{uri}}/auth/reset-password",
552 | "host": [
553 | "{{uri}}"
554 | ],
555 | "path": [
556 | "auth",
557 | "reset-password"
558 | ]
559 | }
560 | },
561 | "response": []
562 | },
563 | {
564 | "name": "Sign In With Changed Password Success [200]",
565 | "event": [
566 | {
567 | "listen": "test",
568 | "script": {
569 | "exec": [
570 | "const responseEncryptionSecret = pm.environment.get(\"response-encryption-secret\");\r",
571 | "const encrypted = pm.response.json();\r",
572 | "const decrypted = CryptoJS.RC4.decrypt(\r",
573 | " encrypted,\r",
574 | " responseEncryptionSecret\r",
575 | " ).toString(CryptoJS.enc.Utf8);\r",
576 | "\r",
577 | "const decryptedJson = JSON.parse(decrypted);\r",
578 | "console.log(decryptedJson);\r",
579 | "\r",
580 | "pm.test(\"Response code is OK\", function () {\r",
581 | " pm.response.to.have.status(200);\r",
582 | "});\r",
583 | "\r",
584 | "pm.test(\"Response contains Access Token\", function () {\r",
585 | " pm.expect(decrypted).to.include('accessToken');\r",
586 | " pm.environment.set(\"access-token\", decryptedJson.accessToken);\r",
587 | "});\r",
588 | "\r",
589 | "pm.test(\"Response contains Refresh Token\", function () {\r",
590 | " pm.expect(decrypted).to.include('refreshToken');\r",
591 | " pm.environment.set(\"refresh-token\", decryptedJson.refreshToken);\r",
592 | "});"
593 | ],
594 | "type": "text/javascript"
595 | }
596 | }
597 | ],
598 | "request": {
599 | "method": "POST",
600 | "header": [],
601 | "body": {
602 | "mode": "raw",
603 | "raw": "{\r\n \"username\": \"{{username}}\",\r\n \"password\": \"{{changed-password}}\"\r\n}",
604 | "options": {
605 | "raw": {
606 | "language": "json"
607 | }
608 | }
609 | },
610 | "url": {
611 | "raw": "{{uri}}/auth/login",
612 | "host": [
613 | "{{uri}}"
614 | ],
615 | "path": [
616 | "auth",
617 | "login"
618 | ]
619 | }
620 | },
621 | "response": []
622 | }
623 | ]
624 | },
625 | {
626 | "name": "Sign Out",
627 | "item": [
628 | {
629 | "name": "Sign Out Success [204]",
630 | "event": [
631 | {
632 | "listen": "test",
633 | "script": {
634 | "exec": [
635 | "pm.test(\"Response code is No Content\", function () {\r",
636 | " pm.response.to.have.status(204);\r",
637 | "});"
638 | ],
639 | "type": "text/javascript"
640 | }
641 | }
642 | ],
643 | "request": {
644 | "method": "POST",
645 | "header": [],
646 | "body": {
647 | "mode": "raw",
648 | "raw": "{\r\n \"token\": \"{{refresh-token}}\"\r\n}",
649 | "options": {
650 | "raw": {
651 | "language": "json"
652 | }
653 | }
654 | },
655 | "url": {
656 | "raw": "{{uri}}/auth/logout",
657 | "host": [
658 | "{{uri}}"
659 | ],
660 | "path": [
661 | "auth",
662 | "logout"
663 | ]
664 | }
665 | },
666 | "response": []
667 | }
668 | ]
669 | },
670 | {
671 | "name": "Delete",
672 | "item": [
673 | {
674 | "name": "Delete User",
675 | "event": [
676 | {
677 | "listen": "test",
678 | "script": {
679 | "exec": [
680 | "pm.test(\"Response code is OK\", function () {\r",
681 | " pm.response.to.have.status(204);\r",
682 | "});\r",
683 | ""
684 | ],
685 | "type": "text/javascript"
686 | }
687 | }
688 | ],
689 | "request": {
690 | "auth": {
691 | "type": "bearer",
692 | "bearer": [
693 | {
694 | "key": "token",
695 | "value": "{{admin-auth-token}}",
696 | "type": "string"
697 | }
698 | ]
699 | },
700 | "method": "DELETE",
701 | "header": [],
702 | "url": {
703 | "raw": "{{uri}}/auth/user?username={{username}}",
704 | "host": [
705 | "{{uri}}"
706 | ],
707 | "path": [
708 | "auth",
709 | "user"
710 | ],
711 | "query": [
712 | {
713 | "key": "username",
714 | "value": "{{username}}"
715 | }
716 | ]
717 | }
718 | },
719 | "response": []
720 | }
721 | ]
722 | }
723 | ]
724 | }
725 | ]
726 | }
727 |
--------------------------------------------------------------------------------
/assets/tests/regression-tests/postman/auth-server-regression.postman_environment_develop.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "c326a9c8-6cb1-4f9e-9361-5131bf8636c8",
3 | "name": "auth-server-regression-develop",
4 | "values": [
5 | {
6 | "key": "uri",
7 | "value": "https://oth-auth-server.herokuapp.com",
8 | "enabled": true
9 | },
10 | {
11 | "key": "auto-verify-enabled",
12 | "value": "true",
13 | "enabled": true
14 | },
15 | {
16 | "key": "username",
17 | "value": "postman-regression-develop",
18 | "enabled": true
19 | },
20 | {
21 | "key": "email",
22 | "value": "postman-regression-develop@oth.com",
23 | "enabled": true
24 | },
25 | {
26 | "key": "password",
27 | "value": "password",
28 | "enabled": true
29 | },
30 | {
31 | "key": "changed-password",
32 | "value": "changed-password",
33 | "enabled": true
34 | },
35 | {
36 | "key": "access-token",
37 | "value": "access-token",
38 | "enabled": true
39 | },
40 | {
41 | "key": "refresh-token",
42 | "value": "refresh-token",
43 | "enabled": true
44 | },
45 | {
46 | "key": "reset-password-token",
47 | "value": "reset-password-token",
48 | "enabled": true
49 | }
50 | ],
51 | "_postman_variable_scope": "environment",
52 | "_postman_exported_at": "2021-02-07T20:04:27.694Z",
53 | "_postman_exported_using": "Postman/8.0.4"
54 | }
55 |
--------------------------------------------------------------------------------
/assets/tests/regression-tests/postman/auth-server-regression.postman_environment_local.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "aad65d89-0bba-4f1b-87b6-a8cb8ddcf099",
3 | "name": "auth-server-regression-local",
4 | "values": [
5 | {
6 | "key": "uri",
7 | "value": "http://localhost:4001",
8 | "enabled": true
9 | },
10 | {
11 | "key": "auto-verify-enabled",
12 | "value": "true",
13 | "enabled": true
14 | },
15 | {
16 | "key": "username",
17 | "value": "postman-regression-local",
18 | "enabled": true
19 | },
20 | {
21 | "key": "email",
22 | "value": "postman-regression-local@oth.com",
23 | "enabled": true
24 | },
25 | {
26 | "key": "password",
27 | "value": "password",
28 | "enabled": true
29 | },
30 | {
31 | "key": "changed-password",
32 | "value": "changed-password",
33 | "enabled": true
34 | },
35 | {
36 | "key": "access-token",
37 | "value": "access-token",
38 | "enabled": true
39 | },
40 | {
41 | "key": "refresh-token",
42 | "value": "refresh-token",
43 | "enabled": true
44 | },
45 | {
46 | "key": "reset-password-token",
47 | "value": "reset-password-token",
48 | "enabled": true
49 | }
50 | ],
51 | "_postman_variable_scope": "environment",
52 | "_postman_exported_at": "2021-02-07T20:04:36.672Z",
53 | "_postman_exported_using": "Postman/8.0.4"
54 | }
55 |
--------------------------------------------------------------------------------
/assets/tests/stress-tests/login.jmx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | false
7 | true
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | continue
17 |
18 | false
19 | -1
20 |
21 | 50
22 | 1
23 | false
24 |
25 |
26 | true
27 |
28 |
29 |
30 | true
31 |
32 |
33 |
34 | false
35 | {
36 | "username": "username",
37 | "password": "password"
38 | }
39 |
40 | =
41 |
42 |
43 |
44 | localhost
45 | 4001
46 | http
47 |
48 | auth/login
49 | POST
50 | true
51 | false
52 | true
53 | false
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | content-type
63 | application/json
64 |
65 |
66 |
67 |
68 |
69 |
70 | false
71 |
72 | saveConfig
73 |
74 |
75 | true
76 | true
77 | true
78 |
79 | true
80 | true
81 | true
82 | true
83 | false
84 | true
85 | true
86 | false
87 | false
88 | false
89 | true
90 | false
91 | false
92 | false
93 | true
94 | 0
95 | true
96 | true
97 | true
98 | true
99 | true
100 | true
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/auth-server.main.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @description holds server main
3 | */
4 |
5 | import { DebugLogUtil, UsageUtil } from '@open-template-hub/common';
6 | import bodyParser from 'body-parser';
7 | import cors from 'cors';
8 | import dotenv from 'dotenv';
9 | import express from 'express';
10 | import { Routes } from './app/route/index.route';
11 |
12 | const debugLogUtil = new DebugLogUtil();
13 |
14 | const env = dotenv.config();
15 | debugLogUtil.log( env.parsed );
16 |
17 | // express init
18 | const app: express.Application = express();
19 |
20 | // public files
21 | app.use( express.static( 'public' ) );
22 |
23 | // parse application/json
24 | app.use( bodyParser.urlencoded( { extended: false } ) );
25 | app.use( bodyParser.json() );
26 | app.use( cors() );
27 |
28 | // mount routes
29 | Routes.mount( app );
30 |
31 | // listen port
32 | const port: string = process.env.PORT || ( '4001' as string );
33 |
34 | app.listen( port, () => {
35 | console.info( 'Auth Server is running on port', port );
36 |
37 | const usageUtil = new UsageUtil();
38 | const memoryUsage = usageUtil.getMemoryUsage();
39 | console.info( `Startup Memory Usage: ${ memoryUsage.toFixed( 2 ) } MB` );
40 | } );
41 |
--------------------------------------------------------------------------------
/dependency-checker.ts:
--------------------------------------------------------------------------------
1 | const { spawnSync } = require( 'child_process' );
2 | const outdatedCmd = spawnSync( 'npm', [ 'outdated' ] );
3 |
4 | const lines = outdatedCmd.stdout.toString().split( '\n' );
5 |
6 | const columnIndexes = [ 0, 0, 0, 0 ];
7 |
8 | let indexOfDependedBy = -1;
9 |
10 | console.log(
11 | '\n' +
12 | ' \n' +
13 | '
\n' +
14 | ' \n' +
15 | '
\n' +
16 | '\n' +
17 | '\n' +
18 | '\n' +
19 | 'Open Template Hub - Auth Server Template v5\n' +
20 | '
\n' +
21 | '(outdated packages)\n' +
22 | '
\n' +
23 | '\n' +
24 | 'Following packages are not updated in the develop branch yet. So, if you want to update outdated packages on your own risk, update the package.json and install dependencies.\n'
25 | );
26 |
27 | for ( const line of lines ) {
28 | if ( line.length === 0 ) {
29 | continue;
30 | }
31 |
32 | if ( lines.indexOf( line ) === 0 ) {
33 | columnIndexes[ 0 ] = line.indexOf( 'Current' );
34 | columnIndexes[ 1 ] = line.indexOf( 'Wanted' ) + 3;
35 | columnIndexes[ 2 ] = line.indexOf( 'Latest' ) + 6;
36 | columnIndexes[ 3 ] = line.indexOf( 'Location' ) + 9;
37 | }
38 |
39 | let modifiedLine = '';
40 |
41 | if ( columnIndexes [ 0 ] >= 0 ) {
42 | const stringParts = line.split( /(\s+)/ );
43 |
44 | modifiedLine += '| ';
45 |
46 | for ( let i = 0; i < stringParts.length; ++i ) {
47 | const part = stringParts[ i ];
48 |
49 | if ( lines.indexOf( line ) === 0 && i < stringParts.length - 1 && stringParts[ i + 1 ] === 'Depended' ) {
50 | indexOfDependedBy = i;
51 | }
52 |
53 | if ( indexOfDependedBy !== -1 && i >= indexOfDependedBy ) {
54 | continue;
55 | }
56 |
57 | if ( part.match( /\s+/ ) ) {
58 | modifiedLine += ' | ';
59 | } else {
60 | modifiedLine += part;
61 | }
62 | }
63 |
64 | modifiedLine += ' |';
65 |
66 | console.log( modifiedLine );
67 | } else {
68 | console.log( modifiedLine );
69 | }
70 |
71 | if ( lines.indexOf( line ) === 0 ) {
72 | console.log( '| --- | --- | --- | --- | --- |' );
73 | }
74 | }
75 |
76 | console.log(
77 | '\n' +
78 | ' | Open Template Hub © 2023 |
\n'
79 | );
80 |
--------------------------------------------------------------------------------
/docs/OUTDATED.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Open Template Hub - Auth Server Template v5
10 |
11 | (outdated packages)
12 |
13 |
14 | Following packages are not updated in the develop branch yet. So, if you want to update outdated packages on your own risk, update the package.json and install dependencies.
15 |
16 | | Package | Current | Wanted | Latest | Location |
17 | | --- | --- | --- | --- | --- |
18 | | @open-template-hub/common | 5.0.10 | 5.0.10 | 5.0.18 | node_modules/@open-template-hub/common |
19 | | @types/express | 4.17.23 | 4.17.23 | 5.0.3 | node_modules/@types/express |
20 | | @types/uuid | 9.0.8 | 9.0.8 | 10.0.0 | node_modules/@types/uuid |
21 | | bcrypt | 5.1.1 | 5.1.1 | 6.0.0 | node_modules/bcrypt |
22 | | body-parser | 1.20.3 | 1.20.3 | 2.2.0 | node_modules/body-parser |
23 | | express | 4.21.2 | 4.21.2 | 5.1.0 | node_modules/express |
24 | | mongoose | 6.13.8 | 6.13.8 | 8.15.1 | node_modules/mongoose |
25 | | nodemon | 2.0.22 | 2.0.22 | 3.1.10 | node_modules/nodemon |
26 | | typescript | 4.9.5 | 4.9.5 | 5.8.3 | node_modules/typescript |
27 | | uuid | 9.0.1 | 9.0.1 | 11.1.0 | node_modules/uuid |
28 |
29 |  | Open Template Hub © 2023 |
30 |
31 |
--------------------------------------------------------------------------------
/docs/REGRESSION_TESTS.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Open Template Hub - Auth Server Template v5
10 |
11 | (postman regression tests)
12 |
13 |
14 | ## Prerequisite
15 |
16 | Please check [README.md](../README.md) to get more information to configure environment
17 |
18 | ## Configuration
19 |
20 | * Import **auth-server-regression.postman_environment_local.json** as environment to Postman
21 |
22 | * Import **auth-server-regression.postman_collection.json** as collection to Postman
23 |
24 | * Update imported environment variables and run collection on Postman
25 |
26 |  | Open Template Hub © 2023 |
27 |
--------------------------------------------------------------------------------
/docs/SOCIAL_LOGIN.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Auth Server Template
9 |
10 | (Social Login Configuration Guide)
11 |
12 |
13 | Follow the instructions to be able to use social login meshanism
14 |
15 | ## Social Login Configurations
16 |
17 | This is the list of pre-tested social logins. Configuring only what you need to use is enough.
18 |
19 | 1. [Google](#1-google)
20 | 2. [GitHub](#2-github)
21 | 3. [Facebook](#3-facebook)
22 | 4. [Twitter](#4-twitter)
23 | 5. [LinkedIn](#5-linkedin)
24 | 6. [Reddit](#6-reddit)
25 | 7. [Dribbble](#7-dribbble)
26 | 8. [Twitch](#8-twitch)
27 |
28 | ### 1. Google
29 |
30 | Create your application from **[console.developers.google.com](https://console.developers.google.com)**, fill the fields in the **SQL** below appropriately and run the query.
31 |
32 | ``` sql
33 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('GOOGLE', 'https://console.developers.google.com');
34 | INSERT INTO oauth_v2_config_params
35 | (
36 | social_login_key,
37 | client_id,
38 | client_secret,
39 | redirect_uri,
40 | login_uri,
41 | access_token_uri,
42 | access_token_json_field_path,
43 | user_data_uri,
44 | external_user_id_json_field_path,
45 | external_user_email_json_field_path,
46 | external_username_json_field_path,
47 | token_type_json_field_path,
48 | requested_with_auth_header,
49 | access_token_request_method
50 | )
51 | VALUES
52 | (
53 | 'GOOGLE',
54 | ,
55 | ,
56 | ,
57 | 'https://accounts.google.com/o/oauth2/v2/auth?client_id={{0}}&state={{1}}&redirect_uri={{2}}&response_type=code&scope=openid%20profile%20email',
58 | 'https://oauth2.googleapis.com/token?client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}&grant_type=authorization_code',
59 | 'access_token',
60 | 'https://people.googleapis.com/v1/people/me?personFields=emailAddresses&access_token={{0}}',
61 | 'emailAddresses.0.metadata.source.id',
62 | 'emailAddresses.0.value',
63 | NULL,
64 | NULL,
65 | false,
66 | 'POST'
67 | );
68 | ```
69 |
70 | ### 2. GitHub
71 |
72 | Create your application from **[github.com/settings/developers](https://github.com/settings/developers)**, fill the fields in the **SQL** below appropriately and run the query.
73 |
74 | ``` sql
75 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('GITHUB', 'https://github.com/settings/developers');
76 | INSERT INTO oauth_v2_config_params
77 | (
78 | social_login_key,
79 | client_id,
80 | client_secret,
81 | redirect_uri,
82 | login_uri,
83 | access_token_uri,
84 | access_token_json_field_path,
85 | user_data_uri,
86 | external_user_id_json_field_path,
87 | external_user_email_json_field_path,
88 | external_username_json_field_path,
89 | token_type_json_field_path,
90 | requested_with_auth_header,
91 | access_token_request_method
92 | )
93 | VALUES (
94 | 'GITHUB',
95 | ,
96 | ,
97 | ,
98 | 'https://github.com/login/oauth/authorize?client_id={{0}}&state={{1}}&redirect_uri={{2}}',
99 | 'https://github.com/login/oauth/access_token?client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}',
100 | 'access_token',
101 | 'https://api.github.com/user',
102 | 'id',
103 | 'email',
104 | 'login',
105 | 'token_type',
106 | true,
107 | 'GET'
108 | );
109 | ```
110 |
111 | ### 3. Facebook
112 |
113 | Create your application from **[developers.facebook.com](https://developers.facebook.com)**, fill the fields in the **SQL** below appropriately and run the query.
114 |
115 | ``` sql
116 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('FACEBOOK', 'https://developers.facebook.com');
117 | INSERT INTO oauth_v2_config_params
118 | (
119 | social_login_key,
120 | client_id,
121 | client_secret,
122 | redirect_uri,
123 | login_uri,
124 | access_token_uri,
125 | access_token_json_field_path,
126 | user_data_uri,
127 | external_user_id_json_field_path,
128 | external_user_email_json_field_path,
129 | external_username_json_field_path,
130 | token_type_json_field_path,
131 | requested_with_auth_header,
132 | access_token_request_method
133 | )
134 | VALUES
135 | (
136 | 'FACEBOOK',
137 | ,
138 | ,
139 | ,
140 | 'https://www.facebook.com/v6.0/dialog/oauth?client_id={{0}}&redirect_uri={{2}}',
141 | 'https://graph.facebook.com/v6.0/oauth/access_token?client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}#_=_',
142 | 'access_token',
143 | 'https://graph.facebook.com/me?fields=id,email&access_token={{0}}',
144 | 'id',
145 | 'email',
146 | NULL,
147 | 'token_type',
148 | false,
149 | 'GET'
150 | );
151 | ```
152 |
153 | ### 4. Twitter
154 |
155 | Create your application from **[developer.twitter.com/en/apps](https://developer.twitter.com/en/apps)**, fill the fields in the **SQL** below appropriately and run the query.
156 |
157 | ``` sql
158 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('TWITTER', 'https://developer.twitter.com/en/apps');
159 | INSERT INTO oauth_v1_config_params
160 | (
161 | social_login_key,
162 | client_id,
163 | client_secret,
164 | redirect_uri,
165 | login_uri,
166 | access_token_uri,
167 | access_token_query_param_field_path,
168 | user_data_uri,
169 | external_user_id_query_param_field_path,
170 | external_user_email_query_param_field_path,
171 | external_username_query_param_field_path,
172 | request_token_uri,
173 | access_token_request_method
174 | )
175 | VALUES
176 | (
177 | 'TWITTER',
178 | ,
179 | ,
180 | ,
181 | 'https://api.twitter.com/oauth/authenticate?oauth_token={{0}}',
182 | 'https://api.twitter.com/oauth/access_token?oauth_token={{0}}&oauth_verifier={{1}}',
183 | 'oauth_token',
184 | 'https://api.twitter.com/1.1/account/verify_credentials.json',
185 | 'user_id',
186 | NULL,
187 | 'screen_name',
188 | 'https://api.twitter.com/oauth/request_token',
189 | 'POST'
190 | );
191 | ```
192 |
193 | ### 5. LinkedIn
194 |
195 | Create your application from **[linkedin.com/developers/apps](https://linkedin.com/developers/apps)**, fill the fields in the **SQL** below appropriately and run the query.
196 |
197 | ``` sql
198 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('LINKEDIN', 'https://linkedin.com/developers/apps');
199 | INSERT INTO oauth_v2_config_params
200 | (
201 | social_login_key,
202 | client_id,
203 | client_secret,
204 | redirect_uri,
205 | login_uri,
206 | access_token_uri,
207 | access_token_json_field_path,
208 | user_data_uri,
209 | external_user_id_json_field_path,
210 | external_user_email_json_field_path,
211 | external_username_json_field_path,
212 | token_type_json_field_path,
213 | requested_with_auth_header,
214 | access_token_request_method
215 | )
216 | VALUES
217 | (
218 | 'LINKEDIN',
219 | ,
220 | ,
221 | ,
222 | 'https://www.linkedin.com/oauth/v2/authorization?client_id={{0}}&state={{1}}&redirect_uri={{2}}&response_type=code&scope=r_liteprofile',
223 | 'https://www.linkedin.com/oauth/v2/accessToken?grant_type=authorization_code&client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}',
224 | 'access_token',
225 | 'https://api.linkedin.com/v2/me?projection=(id)',
226 | 'id',
227 | NULL,
228 | NULL,
229 | NULL,
230 | true,
231 | 'POST'
232 | );
233 | ```
234 |
235 | ### 6. Reddit
236 |
237 | Create your application from **[reddit.com/prefs/apps](https://reddit.com/prefs/apps)**, fill the fields in the **SQL** below appropriately and run the query.
238 |
239 | ``` sql
240 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('REDDIT', 'https://reddit.com/prefs/apps');
241 | INSERT INTO oauth_v2_config_params
242 | (
243 | social_login_key,
244 | client_id,
245 | client_secret,
246 | redirect_uri,
247 | login_uri,
248 | access_token_uri,
249 | access_token_json_field_path,
250 | user_data_uri,
251 | external_user_id_json_field_path,
252 | external_user_email_json_field_path,
253 | external_username_json_field_path,
254 | token_type_json_field_path,
255 | requested_with_auth_header,
256 | access_token_request_method
257 | )
258 | VALUES
259 | (
260 | 'REDDIT',
261 | ,
262 | ,
263 | ,
264 | 'https://old.reddit.com/api/v1/authorize?client_id={{0}}&state={{1}}&redirect_uri={{2}}&response_type=code&scope=identity&duration=temporary',
265 | 'https://{{0}}:{{1}}@www.reddit.com/api/v1/access_token?grant_type=authorization_code&code={{3}}&redirect_uri={{2}}',
266 | 'access_token',
267 | 'https://oauth.reddit.com/api/v1/me',
268 | 'id',
269 | NULL,
270 | 'name',
271 | 'token_type',
272 | true,
273 | 'POST'
274 | );
275 | ```
276 |
277 | ### 7. Dribbble
278 |
279 | Create your application from **[dribbble.com/account/applications](https://dribbble.com/account/applications)**, fill the fields in the **SQL** below appropriately and run the query.
280 |
281 | ``` sql
282 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('DRIBBBLE', 'https://dribbble.com/account/applications');
283 | INSERT INTO oauth_v2_config_params
284 | (
285 | social_login_key,
286 | client_id,
287 | client_secret,
288 | redirect_uri,
289 | login_uri,
290 | access_token_uri,
291 | access_token_json_field_path,
292 | user_data_uri,
293 | external_user_id_json_field_path,
294 | external_user_email_json_field_path,
295 | external_username_json_field_path,
296 | token_type_json_field_path,
297 | requested_with_auth_header,
298 | access_token_request_method
299 | )
300 | VALUES
301 | (
302 | 'DRIBBBLE',
303 | ,
304 | ,
305 | ,
306 | 'https://dribbble.com/oauth/authorize?client_id={{0}}&state={{1}}&redirect_uri={{2}}&scope=public',
307 | 'https://dribbble.com/oauth/token?client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}',
308 | 'access_token',
309 | 'https://api.dribbble.com/v2/user',
310 | 'id',
311 | NULL,
312 | 'login',
313 | 'token_type',
314 | true,
315 | 'POST'
316 | );
317 | ```
318 |
319 | ### 8. Twitch
320 |
321 | Create your application from **[dev.twitch.tv/console/apps](https://dev.twitch.tv/console/apps)**, fill the fields in the **SQL** below appropriately and run the query.
322 |
323 | ``` sql
324 | INSERT INTO social_logins (social_login_key, developer_notes) VALUES ('TWITCH', 'https://dev.twitch.tv/console/apps');
325 | INSERT INTO oauth_v2_config_params
326 | (
327 | social_login_key,
328 | client_id,
329 | client_secret,
330 | redirect_uri,
331 | login_uri,
332 | access_token_uri,
333 | access_token_json_field_path,
334 | user_data_uri,
335 | external_user_id_json_field_path,
336 | external_user_email_json_field_path,
337 | external_username_json_field_path,
338 | token_type_json_field_path,
339 | requested_with_auth_header,
340 | access_token_request_method
341 | )
342 | VALUES
343 | (
344 | 'TWITCH',
345 | ,
346 | ,
347 | ,
348 | 'https://id.twitch.tv/oauth2/authorize?client_id={{0}}&state={{1}}&redirect_uri={{2}}&response_type=code&scope=user_read',
349 | 'https://id.twitch.tv/oauth2/token?client_id={{0}}&client_secret={{1}}&redirect_uri={{2}}&code={{3}}&grant_type=authorization_code',
350 | 'access_token',
351 | 'https://api.twitch.tv/helix/users',
352 | 'data.0.id',
353 | NULL,
354 | 'data.0.login',
355 | 'token_type',
356 | true,
357 | 'POST'
358 | );
359 | ```
360 |
--------------------------------------------------------------------------------
/env.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if [ ! -f .env ]; then
4 | echo "Generating .env file.."
5 | touch .env
6 | {
7 | echo "PORT=4001"
8 |
9 | echo "PROJECT=OTH"
10 | echo "MODULE=AuthServer"
11 | echo "ENVIRONMENT=Local"
12 |
13 | echo "CLIENT_URL=http://localhost:4200"
14 | echo "ADMIN_CLIENT_URLS=http://localhost:4202"
15 |
16 | echo "DATABASE_URL={Database Connection Url}"
17 | echo "POSTGRESQL_CONNECTION_LIMIT={Postgresql Connection Limit}"
18 |
19 | echo "CLOUDAMQP_APIKEY={MQ Api Key}"
20 | echo "CLOUDAMQP_URL={MQ Connection Url}"
21 |
22 | echo "AUTH_SERVER_QUEUE_CHANNEL=oth_auth_queue"
23 | echo "ORCHESTRATION_SERVER_QUEUE_CHANNEL=oth_orchestration_queue"
24 |
25 | echo "REDISCLOUD_URL={Redis Connection Url}"
26 | echo "REDIS_CONNECTION_LIMIT={Redis Connection Limit}"
27 |
28 | echo "AUTO_VERIFY=false"
29 |
30 | echo "ACCESS_TOKEN_EXPIRE=1hour"
31 | echo "ACCESS_TOKEN_SECRET={Access Token Secret}"
32 |
33 | echo "REFRESH_TOKEN_EXPIRE=30days"
34 | echo "REFRESH_TOKEN_SECRET={Refresh Token Secret}"
35 |
36 | echo "JOIN_TEAM_TOKEN_EXPIRE=10days"
37 | echo "JOIN_TEAM_TOKEN_SECRET={Join Team Token Secret}"
38 |
39 | echo "RESET_PASSWORD_TOKEN_EXPIRE=1day"
40 | echo "RESET_PASSWORD_TOKEN_SECRET={Reset Token Secret}"
41 |
42 | echo "VERIFICATION_TOKEN_SECRET={Verification Token Secret"
43 |
44 | echo "RESPONSE_ENCRYPTION_SECRET={Response Encryption Secret}"
45 |
46 | echo "PREAUTH_TOKEN_SECRET={Pre Auth Token Secret}"
47 |
48 | echo "TWO_FACTOR_EXPIRE=90"
49 | echo "TWO_FACTOR_CODE_LENGTH=5"
50 | echo "TWO_FACTOR_CODE_TYPE=numeric"
51 | } >>.env
52 | else
53 | echo ".env file already exists. Nothing to do..."
54 | fi
55 |
--------------------------------------------------------------------------------
/environment.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DbArgs,
3 | EnvArgs,
4 | ExtendedArgs,
5 | TokenArgs,
6 | TwoFactorArgs,
7 | } from '@open-template-hub/common';
8 |
9 | export class Environment {
10 | constructor(private _args: EnvArgs = {} as EnvArgs) {
11 | const tokenArgs = {
12 | accessTokenExpire: process.env.ACCESS_TOKEN_EXPIRE,
13 | accessTokenSecret: process.env.ACCESS_TOKEN_SECRET,
14 | refreshTokenExpire: process.env.REFRESH_TOKEN_EXPIRE,
15 | refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
16 | joinTeamTokenSecretExpire: process.env.JOIN_TEAM_TOKEN_EXPIRE,
17 | joinTeamTokenSecret: process.env.JOIN_TEAM_TOKEN_SECRET,
18 |
19 | resetPasswordTokenExpire: process.env.RESET_PASSWORD_TOKEN_EXPIRE,
20 | resetPasswordTokenSecret: process.env.RESET_PASSWORD_TOKEN_SECRET,
21 |
22 | responseEncryptionSecret: process.env.RESPONSE_ENCRYPTION_SECRET,
23 |
24 | verificationTokenSecret: process.env.VERIFICATION_TOKEN_SECRET,
25 |
26 | preAuthTokenSecret: process.env.PREAUTH_TOKEN_SECRET,
27 | } as TokenArgs;
28 |
29 | const dbArgs = {
30 | mongoDbConnectionLimit: process.env.MONGODB_CONNECTION_LIMIT,
31 | mongoDbUri: process.env.MONGODB_URI,
32 |
33 | postgresqlUri: process.env.DATABASE_URL,
34 | postgresqlConnectionLimit: process.env.POSTGRESQL_CONNECTION_LIMIT,
35 |
36 | redisUri: process.env.REDISCLOUD_URL,
37 | redisConnectionLimit: process.env.REDIS_CONNECTION_LIMIT,
38 | } as DbArgs;
39 |
40 | const extendedArgs = {
41 | clientUrl: process.env.CLIENT_URL,
42 | } as ExtendedArgs;
43 |
44 | const mqArgs = {
45 | messageQueueConnectionUrl: process.env.CLOUDAMQP_URL,
46 | authServerMessageQueueChannel: process.env.AUTH_SERVER_QUEUE_CHANNEL,
47 | orchestrationServerMessageQueueChannel:
48 | process.env.ORCHESTRATION_SERVER_QUEUE_CHANNEL,
49 | };
50 |
51 | const twoFactorCodeArgs = {
52 | twoFactorCodeExpire: process.env.TWO_FACTOR_EXPIRE,
53 | twoFactorCodeLength: process.env.TWO_FACTOR_CODE_LENGTH,
54 | twoFactorCodeType: process.env.TWO_FACTOR_CODE_TYPE,
55 | } as TwoFactorArgs;
56 |
57 | this._args = {
58 | tokenArgs,
59 | dbArgs,
60 | mqArgs,
61 | extendedArgs,
62 | twoFactorCodeArgs,
63 | } as EnvArgs;
64 | }
65 |
66 | args = () => {
67 | return this._args;
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auth-server-template",
3 | "description": "Auth Server Template is a generic open-source authentication server that has a simple yet powerful design to connect your business with all OAuth 2.0 and OAuth supporting third-party companies (like Google, Facebook, Twitter, or LinkedIn). It also supports a basic username-password authentication system.",
4 | "version": "5.0.1",
5 | "license": "MIT",
6 | "main": "auth-server.main.ts",
7 | "scripts": {
8 | "start": "node version-generator.ts > ./version.ts && ts-node auth-server.main.ts",
9 | "startLocal": "nodemon --exec DEBUG=* ts-node auth-server.main.ts",
10 | "postmanLocal": "mkdir -p -- ./assets/test-results && touch ./assets/test-results/postman-dark.html && touch ./assets/test-results/postman.html && newman run assets/tests/regression-tests/postman/auth-server-regression.postman_collection.json -e assets/tests/regression-tests/postman/auth-server-regression.postman_environment_local.json -r htmlextra --reporter-htmlextra-export ./assets/test-results/postman.html --reporter-htmlextra-darkTheme > ./assets/test-results/postman-dark.html --env-var $npm_config_adminAuthToken --env-var $npm_config_responseEncryptionSecret",
11 | "postmanDevelop": "mkdir -p -- ./assets/test-results && touch ./assets/test-results/postman-dark.html && touch ./assets/test-results/postman.html && newman run assets/tests/regression-tests/postman/auth-server-regression.postman_collection.json -e assets/tests/regression-tests/postman/auth-server-regression.postman_environment_develop.json -r htmlextra --reporter-htmlextra-export ./assets/test-results/postman.html --reporter-htmlextra-darkTheme > ./assets/test-results/postman-dark.html --env-var $npm_config_adminAuthToken --env-var $npm_config_responseEncryptionSecret",
12 | "outdated": "node dependency-checker.ts > docs/OUTDATED.md"
13 | },
14 | "dependencies": {
15 | "@open-template-hub/common": "5.0.10",
16 | "@types/bcrypt": "^5.0.0",
17 | "@types/capitalize": "^2.0.0",
18 | "@types/cors": "^2.8.13",
19 | "@types/express": "^4.17.15",
20 | "@types/mongoose": "^5.11.97",
21 | "@types/uuid": "^9.0.0",
22 | "axios": "^1.2.2",
23 | "bcrypt": "^5.1.0",
24 | "body-parser": "^1.20.1",
25 | "capitalize": "^2.0.4",
26 | "cors": "^2.8.5",
27 | "crypto": "^1.0.1",
28 | "dotenv": "^16.0.3",
29 | "express": "^4.18.2",
30 | "express-promise-router": "^4.1.1",
31 | "mongoose": "^6.8.3",
32 | "oauth-1.0a": "^2.2.6",
33 | "ts-node": "^10.9.1",
34 | "typescript": "^4.9.4",
35 | "uuid": "^9.0.0"
36 | },
37 | "devDependencies": {
38 | "nodemon": "^2.0.20"
39 | },
40 | "git repository": "https://github.com/open-template-hub/auth-server-template",
41 | "repository": {
42 | "type": "git",
43 | "url": "git+https://github.com/open-template-hub/auth-server-template.git"
44 | },
45 | "keywords": [
46 | "nodejs",
47 | "template",
48 | "oauth",
49 | "oauth2 express",
50 | "authentication",
51 | "server facebook-login",
52 | "twitter-login google-login",
53 | "social-login linkedin-login",
54 | "nodejs-express github-login",
55 | "twitch-login",
56 | "backend",
57 | "rest",
58 | "node",
59 | "nodejs",
60 | "typescript",
61 | "template",
62 | "server template",
63 | "open template hub"
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | "target": "es5",
5 | /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
6 | "module": "commonjs",
7 | /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
8 | // "lib": [], /* Specify library files to be included in the compilation. */
9 | // "allowJs": true, /* Allow javascript files to be compiled. */
10 | // "checkJs": true, /* Report errors in .js files. */
11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
14 | "sourceMap": true,
15 | /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "out",
18 | /* Redirect output structure to the directory. */
19 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
20 | // "composite": true, /* Enable project compilation */
21 | // "incremental": true, /* Enable incremental compilation */
22 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
23 | // "removeComments": true, /* Do not emit comments to output. */
24 | // "noEmit": true, /* Do not emit outputs. */
25 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
26 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
27 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
28 |
29 | /* Strict Type-Checking Options */
30 | "strict": true,
31 | /* Enable all strict type-checking options. */
32 | "noImplicitAny": true,
33 | /* Raise error on expressions and declarations with an implied 'any' type. */
34 | // "strictNullChecks": true, /* Enable strict null checks. */
35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
36 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
37 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
38 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
39 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
40 |
41 | /* Additional Checks */
42 | // "noUnusedLocals": true, /* Report errors on unused locals. */
43 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
44 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
45 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
46 |
47 | /* Module Resolution Options */
48 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
49 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
50 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
51 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
52 | // "typeRoots": [], /* List of folders to include type definitions from. */
53 | // "types": [], /* Type declaration files to be included in compilation. */
54 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
55 | "esModuleInterop": true
56 | /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
57 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
58 |
59 | /* Source Map Options */
60 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
61 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
62 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
63 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
64 |
65 | /* Experimental Options */
66 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
67 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/version-generator.ts:
--------------------------------------------------------------------------------
1 | console.log( 'export const version = \'' + require( './package.json' ).version + '\';' );
2 |
--------------------------------------------------------------------------------
/version.ts:
--------------------------------------------------------------------------------
1 | export const version = '3.0.1';
2 |
--------------------------------------------------------------------------------