Compare commits

..

No commits in common. "develop" and "v1.1.0" have entirely different histories.

44 changed files with 556 additions and 15343 deletions

View File

@ -1 +0,0 @@
{ "extends": ["@commitlint/config-conventional"] }

View File

@ -1,16 +0,0 @@
{
"extends": ["conventions", "prettier"],
"plugins": ["prettier", "import", "unicorn"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"env": {
"node": true
},
"rules": {
"prettier/prettier": "error",
"import/extensions": ["error", "always"],
"unicorn/prefer-node-protocol": "error"
}
}

1
.gitattributes vendored
View File

@ -1 +0,0 @@
* text=auto eol=lf

54
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,54 @@
# 💡 Contributing
Thanks a lot for your interest in contributing to **Thream/socketio-jwt**! 🎉
## Code of Conduct
**Thream** has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](https://github.com/Thream/Thream/blob/master/.github/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
## Open Development
All work on **Thream** happens directly on [GitHub](https://github.com/Thream). Both core team members and external contributors send pull requests which go through the same review process.
## Types of contributions
- Reporting a bug.
- Suggest a new feature idea.
- Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...).
- Improve structure/format/performance/refactor/tests of the code.
## Pull Requests
- **Please first discuss** the change you wish to make via [issue](https://github.com/Thream/socketio-jwt/issues) before making a change. It might avoid a waste of your time.
- Ensure your code respect [Typescript Standard Style](https://www.npmjs.com/package/ts-standard).
- Make sure your **code passes the tests**.
If you're adding new features to **Thream/socketio-jwt**, please include tests.
## Commits
The commit message guidelines respect [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) and [Semantic Versioning](https://semver.org/) for releases.
### Types
Types define which kind of changes you made to the project.
| Types | Description |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| feat | A new feature. |
| fix | A bug fix. |
| docs | Documentation only changes. |
| style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). |
| refactor | A code change that neither fixes a bug nor adds a feature. |
| perf | A code change that improves performance. |
| test | Adding missing tests or correcting existing tests. |
| build | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm). |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs). |
| chore | Other changes that don't modify src or test files. |
| revert | Reverts a previous commit. |
### Scopes
Scopes define what part of the code changed.

View File

@ -1,8 +1,7 @@
--- ---
name: "🐛 Bug Report" name: '🐛 Bug Report'
about: "Report an unexpected problem or unintended behavior." about: 'Report an unexpected problem or unintended behavior.'
title: "[Bug]" labels: 'bug'
labels: "bug"
--- ---
<!-- <!--

View File

@ -1,18 +1,20 @@
--- ---
name: "📜 Documentation" name: '📜 Documentation'
about: "Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...)." about: 'Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...).'
title: "[Documentation]" labels: 'documentation'
labels: "documentation"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!--
Please make sure your issue has not already been fixed.
## Documentation Please place an x (no spaces - [x]) in all [ ] that apply.
-->
<!-- Please uncomment the type of documentation problem this issue address --> ### Documentation :
<!-- Documentation is Missing --> - [ ] Is Missing
<!-- Documentation is Confusing --> - [ ] Is Confusing
<!-- Documentation has Typo errors --> - [ ] Has Typo errors
- [ ] Not Sure?
## Proposal ### Proposal

View File

@ -1,20 +1,19 @@
--- ---
name: "✨ Feature Request" name: '✨ Feature Request'
about: "Suggest a new feature idea." about: 'Suggest a new feature idea.'
title: "[Feature]" labels: 'feature request'
labels: "feature request"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!-- Please make sure your issue has not already been fixed. -->
## Description ### Description
<!-- A clear and concise description of the problem or missing capability... --> <!-- A clear and concise description of the problem or missing capability... -->
## Describe the solution you'd like ### Describe the solution you'd like
<!-- If you have a solution in mind, please describe it. --> <!-- If you have a solution in mind, please describe it. -->
## Describe alternatives you've considered ### Describe alternatives you've considered
<!-- Have you considered any alternative solutions or workarounds? --> <!-- Have you considered any alternative solutions or workarounds? -->

View File

@ -1,20 +1,21 @@
--- ---
name: "🔧 Improvement" name: '🔧 Improvement'
about: "Improve structure/format/performance/refactor/tests of the code." about: 'Improve structure/format/performance/refactor/tests of the code.'
title: "[Improvement]" labels: 'improvement'
labels: "improvement"
--- ---
<!-- Please make sure your issue has not already been fixed. --> <!--
Please make sure your issue has not already been fixed.
## Type of Improvement Please place an x (no spaces - [x]) in all [ ] that apply.
-->
<!-- Please uncomment the type of improvements this issue address --> ### Type of Improvement :
<!-- Files and Folders Structure --> - [ ] Files and Folders Structure
<!-- Performance --> - [ ] Performance
<!-- Refactoring code --> - [ ] Refactoring code
<!-- Tests --> - [ ] Tests
<!-- Not Sure? --> - [ ] Not Sure?
## Proposal ### Proposal

View File

@ -1,8 +1,7 @@
--- ---
name: "🙋 Question" name: '🙋 Question'
about: "Further information is requested." about: 'Further information is requested.'
title: "[Question]" labels: 'question'
labels: "question"
--- ---
### Question ### Question

View File

@ -1,7 +1,27 @@
<!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. --> <!--
# What changes this PR introduce? Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time.
## List any relevant issue numbers Before submitting your contribution, please take a moment to review this document:
https://github.com/Thream/socketio-jwt/blob/master/.github/CONTRIBUTING.md
## Is there anything you'd like reviewers to focus on? Please place an x (no spaces - [x]) in all [ ] that apply.
-->
### What type of change does this PR introduce?
- [ ] Bugfix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Not Sure?
### Does this PR introduce breaking changes?
- [ ] Yes
- [ ] No
### List any relevant issue numbers:
### Description:

13
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# For more information see: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'

View File

@ -1,27 +0,0 @@
name: "Build"
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
build:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v4.0.0"
- name: "Setup Node.js"
uses: "actions/setup-node@v3.8.1"
with:
node-version: "20.x"
cache: "npm"
- name: "Install dependencies"
run: "npm clean-install"
- name: "Build"
run: "npm run build"
- run: "npm run build:typescript"

20
.github/workflows/commitlint.yml vendored Normal file
View File

@ -0,0 +1,20 @@
# For more information see: https://github.com/marketplace/actions/commit-linter
name: 'Lint Commit Messages'
on:
push:
branches: [master, develop]
pull_request_review:
branches: [master, develop]
types: [submitted]
jobs:
commitlint:
if: ${{ (github.event_name == 'push') || (github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && (github.event.review.author_association == 'COLLABORATOR' || github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER') && !github.event.pull_request.draft && github.event.pull_request.state == 'open') }}
runs-on: ubuntu-latest
steps:
- uses: 'actions/checkout@v2'
with:
fetch-depth: 0
- uses: 'wagoid/commitlint-github-action@v2'

View File

@ -1,28 +0,0 @@
name: "Lint"
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
lint:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v4.0.0"
- name: "Setup Node.js"
uses: "actions/setup-node@v3.8.1"
with:
node-version: "20.x"
cache: "npm"
- name: "Install dependencies"
run: "npm clean-install"
- run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- run: "npm run lint:editorconfig"
- run: "npm run lint:markdown"
- run: "npm run lint:eslint"
- run: "npm run lint:prettier"

47
.github/workflows/nodejs.yml vendored Normal file
View File

@ -0,0 +1,47 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: 'Node.js CI'
on:
push:
branches: [master, develop]
pull_request_review:
branches: [master, develop]
types: [submitted]
jobs:
ci_app:
if: ${{ (github.event_name == 'push') || (github.event_name == 'pull_request_review' && github.event.review.state == 'approved' && (github.event.review.author_association == 'COLLABORATOR' || github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER') && !github.event.pull_request.draft && github.event.pull_request.state == 'open') }}
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: 'actions/checkout@v2'
- name: Use Node.js ${{ matrix.node-version }}
uses: 'actions/setup-node@v2.1.4'
with:
node-version: ${{ matrix.node-version }}
- name: 'Cache dependencies'
uses: 'actions/cache@v2'
with:
path: |
**/node_modules
key: ${{ runner.os }}-${{ hashFiles('**/package.json') }}
- name: 'Install dependencies'
run: 'npm install'
- name: 'Lint'
run: 'npm run lint'
- name: 'Build'
run: 'npm run build'
- name: 'Run the tests and generate coverage report'
run: 'npm test'
- name: 'Upload coverage to Codecov'
uses: 'codecov/codecov-action@v1'

30
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,30 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: 'Node.js Package'
on:
release:
types: [created]
jobs:
publish-npm:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2'
- name: 'Cache dependencies'
uses: 'actions/cache@v2'
with:
path: '**/node_modules'
key: ${{ runner.os }}-${{ hashFiles('**/package.json') }}
- uses: 'actions/setup-node@v2.1.2'
with:
node-version: 14
registry-url: 'https://registry.npmjs.org/'
- run: 'npm install'
- run: 'npm run build'
- run: 'npm publish --access public'
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View File

@ -1,39 +0,0 @@
name: "Release"
on:
push:
branches: [master]
jobs:
release:
runs-on: "ubuntu-latest"
permissions:
contents: "write"
issues: "write"
pull-requests: "write"
id-token: "write"
steps:
- uses: "actions/checkout@v4.0.0"
- name: "Setup Node.js"
uses: "actions/setup-node@v3.8.1"
with:
node-version: "20.x"
cache: "npm"
- name: "Install dependencies"
run: "npm clean-install"
- name: "Build Package"
run: "npm run build"
- run: "npm run build:typescript"
- name: "Verify the integrity of provenance attestations and registry signatures for installed dependencies"
run: "npm audit signatures"
- name: "Release"
run: "npm run release"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,28 +0,0 @@
name: "Test"
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
test:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v4.0.0"
- name: "Setup Node.js"
uses: "actions/setup-node@v3.8.1"
with:
node-version: "20.x"
cache: "npm"
- name: "Install dependencies"
run: "npm clean-install"
- name: "Build"
run: "npm run build"
- name: "Test"
run: "npm run test"

34
.gitignore vendored
View File

@ -2,34 +2,34 @@
# dependencies # dependencies
node_modules node_modules
.npm .pnp
.pnp.js
.yarn
# production # production
build build
.swc
# testing # testing
coverage coverage
.nyc_output
# envs
.env
.env.production
# debug # debug
npm-debug.log* npm-debug.log*
yarn-debug.log*
yarn-error.log*
# IDEs and editors # lockfiles
/.idea package-lock.json
.project yarn.lock
.classpath pnpm-lock.yaml
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode # editors
.vscode/* .vscode
!.vscode/settings.json .theia
!.vscode/tasks.json .idea
!.vscode/launch.json
!.vscode/extensions.json
# misc # misc
.DS_Store .DS_Store

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:commit -- --edit

View File

@ -1,6 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:staged
npm run build
npm run build:typescript

View File

@ -1,6 +0,0 @@
{
"*": ["editorconfig-checker"],
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
"*.{json,jsonc,yml,yaml}": ["prettier --write"],
"*.{md,mdx}": ["prettier --write", "markdownlint-cli2 --fix"]
}

View File

@ -1,11 +0,0 @@
{
"config": {
"extends": "markdownlint/style/prettier",
"relative-links": true,
"default": true,
"MD033": false
},
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
}

2
.npmrc
View File

@ -1,2 +1,2 @@
package-lock=false
save-exact=true save-exact=true
provenance=true

View File

@ -1,3 +0,0 @@
{
"semi": false
}

View File

@ -1,19 +0,0 @@
{
"branches": ["master"],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
"@semantic-release/npm",
"@semantic-release/github"
]
}

13
.swcrc
View File

@ -1,13 +0,0 @@
{
"sourceMaps": true,
"jsc": {
"parser": {
"syntax": "typescript",
"dynamicImport": true
},
"target": "esnext"
},
"module": {
"type": "es6"
}
}

View File

@ -1,8 +0,0 @@
{
"recommendations": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"davidanson.vscode-markdownlint"
]
}

11
.vscode/settings.json vendored
View File

@ -1,11 +0,0 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.preferences.importModuleSpecifierEnding": "js",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.configPath": ".prettierrc.json",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"eslint.options": { "ignorePath": ".gitignore" }
}

2
.yarnrc Normal file
View File

@ -0,0 +1,2 @@
install.no-lockfile true
save-exact true

19
CHANGELOG.md Normal file
View File

@ -0,0 +1,19 @@
# Changelog
## [1.1.0](https://github.com/Thream/socketio-jwt/compare/v1.0.1...v1.1.0) (2021-01-07)
### Features
- add algorithms option ([abbabc5](https://github.com/Thream/socketio-jwt/commit/abbabc588e3ea8b906fa0a0dcc83c91a3b5b5ea8))
- add support for jwks-rsa ([#1](https://github.com/Thream/socketio-jwt/issues/1)) ([261e8d6](https://github.com/Thream/socketio-jwt/commit/261e8d66e2ec6fefb77429abcef8f846d996ecac))
- improve types by extending socket.io module ([#6](https://github.com/Thream/socketio-jwt/issues/6)) ([84b523f](https://github.com/Thream/socketio-jwt/commit/84b523f4348c81933887f0dc700f438c84bd779a))
## [1.0.1](https://github.com/Thream/socketio-jwt/compare/v1.0.0...v1.0.1) (2020-12-29)
### Documentation
- fix usage section by correctly importing `authorize`
## [1.0.0](https://github.com/Thream/socketio-jwt/compare/v4.6.2...v1.0.0) (2020-12-29)
Initial release.

View File

@ -1,132 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<contact@theoludwig.fr>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][mozilla coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][faq]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[mozilla coc]: https://github.com/mozilla/diversity
[faq]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -1,32 +0,0 @@
# 💡 Contributing
Thanks a lot for your interest in contributing to **Thream/socketio-jwt**! 🎉
## Code of Conduct
**Thream** has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
## Open Development
All work on **Thream** happens directly on [GitHub](https://github.com/Thream). Both core team members and external contributors send pull requests which go through the same review process.
## Types of contributions
- Reporting a bug.
- Suggest a new feature idea.
- Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...).
- Improve structure/format/performance/refactor/tests of the code.
## Pull Requests
- **Please first discuss** the change you wish to make via [issue](https://github.com/Thream/socketio-jwt/issues) before making a change. It might avoid a waste of your time.
- Ensure your code respect linting.
- Make sure your **code passes the tests**.
If you're adding new features to **Thream/socketio-jwt**, please include tests.
## Commits
The commit message guidelines adheres to [Conventional Commits](https://www.conventionalcommits.org/) and [Semantic Versioning](https://semver.org/) for releases.

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) Auth0, Inc. <support@auth0.com> (<https://auth0.com/>) and Thream contributors Copyright (c) Auth0, Inc. <support@auth0.com> (http://auth0.com) and Thream contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

109
README.md
View File

@ -5,38 +5,26 @@
</p> </p>
<p align="center"> <p align="center">
<strong>⚠️ This project is not maintained anymore, you can still use the code as you wish and fork it to maintain it yourself.</strong> <a href="https://github.com/Thream/socketio-jwt/actions?query=workflow%3A%22Node.js+CI%22"><img src="https://github.com/Thream/socketio-jwt/workflows/Node.js%20CI/badge.svg" alt="Node.js CI" /></a>
</p> <a href="https://codecov.io/gh/Thream/socketio-jwt"><img src="https://codecov.io/gh/Thream/socketio-jwt/branch/develop/graph/badge.svg" alt="codecov" /></a>
<a href="https://dependabot.com/"><img src="https://badgen.net/github/dependabot/Thream/socketio-jwt?icon=dependabot" alt="Dependabot badge" /></a>
<p align="center">
<a href="./CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" /></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
<a href="./CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Contributor Covenant" /></a>
<br/>
<a href="https://github.com/Thream/socketio-jwt/actions/workflows/build.yml"><img src="https://github.com/Thream/socketio-jwt/actions/workflows/build.yml/badge.svg?branch=develop" /></a>
<a href="https://github.com/Thream/socketio-jwt/actions/workflows/lint.yml"><img src="https://github.com/Thream/socketio-jwt/actions/workflows/lint.yml/badge.svg?branch=develop" /></a>
<a href="https://github.com/Thream/socketio-jwt/actions/workflows/test.yml"><img src="https://github.com/Thream/socketio-jwt/actions/workflows/test.yml/badge.svg?branch=develop" /></a>
<br />
<a href="https://conventionalcommits.org"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg" alt="Conventional Commits" /></a>
<a href="https://github.com/semantic-release/semantic-release"><img src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg" alt="semantic-release" /></a>
<a href="https://www.npmjs.com/package/@thream/socketio-jwt"><img src="https://img.shields.io/npm/v/@thream/socketio-jwt.svg" alt="npm version"></a> <a href="https://www.npmjs.com/package/@thream/socketio-jwt"><img src="https://img.shields.io/npm/v/@thream/socketio-jwt.svg" alt="npm version"></a>
<a href="https://www.npmjs.com/package/ts-standard"><img alt="TypeScript Standard Style" src="https://camo.githubusercontent.com/f87caadb70f384c0361ec72ccf07714ef69a5c0a/68747470733a2f2f62616467656e2e6e65742f62616467652f636f64652532307374796c652f74732d7374616e646172642f626c75653f69636f6e3d74797065736372697074"/></a>
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
<a href="https://conventionalcommits.org"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg" alt="Conventional Commits" /></a>
<a href="https://github.com/Thream/Thream/blob/master/.github/CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Contributor Covenant" /></a>
</p> </p>
## 📜 About ## 📜 About
Authenticate socket.io incoming connections with JWTs. Authenticate socket.io incoming connections with JWTs.
This repository was originally forked from [auth0-socketio-jwt](https://github.com/auth0-community/auth0-socketio-jwt) and it is not intended to take any credit but to improve the code from now on. Compatible with `socket.io >= 3.0.0`.
## Prerequisites This repository was originally forked from [auth0-socketio-jwt](https://github.com/auth0-community/auth0-socketio-jwt) & it is not intended to take any credit but to improve the code from now on.
- [Node.js](https://nodejs.org/) >= 16.0.0
- [Socket.IO](https://socket.io/) >= 3.0.0
## 💾 Install ## 💾 Install
**Note:** It is a package that is recommended to use/install on both the client and server sides.
```sh ```sh
npm install --save @thream/socketio-jwt npm install --save @thream/socketio-jwt
``` ```
@ -46,24 +34,24 @@ npm install --save @thream/socketio-jwt
### Server side ### Server side
```ts ```ts
import { Server } from "socket.io" import { Server } from 'socket.io'
import { authorize } from "@thream/socketio-jwt" import { authorize } from '@thream/socketio-jwt'
const io = new Server(9000) const io = new Server(9000)
io.use( io.use(
authorize({ authorize({
secret: "your secret or public key", secret: 'your secret or public key'
}), })
) )
io.on("connection", async (socket) => { io.on('connection', async (socket) => {
// jwt payload of the connected client // jwt payload of the connected client
console.log(socket.decodedToken) console.log(socket.decodedToken)
const clients = await io.sockets.allSockets() const clients = await io.sockets.allSockets()
if (clients != null) { if (clients != null) {
for (const clientId of clients) { for (const clientId of clients) {
const client = io.sockets.sockets.get(clientId) const client = io.sockets.sockets.get(clientId)
client?.emit("messages", { message: "Success!" }) client?.emit('messages', { message: 'Success!' })
// we can access the jwt payload of each connected client // we can access the jwt payload of each connected client
console.log(client?.decodedToken) console.log(client?.decodedToken)
} }
@ -74,12 +62,12 @@ io.on("connection", async (socket) => {
### Server side with `jwks-rsa` (example) ### Server side with `jwks-rsa` (example)
```ts ```ts
import jwksClient from "jwks-rsa" import jwksClient from 'jwks-rsa'
import { Server } from "socket.io" import { Server } from 'socket.io'
import { authorize } from "@thream/socketio-jwt" import { authorize } from '@thream/socketio-jwt'
const client = jwksClient({ const client = jwksClient({
jwksUri: "https://sandrino.auth0.com/.well-known/jwks.json", jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json'
}) })
const io = new Server(9000) const io = new Server(9000)
@ -87,70 +75,37 @@ io.use(
authorize({ authorize({
secret: async (decodedToken) => { secret: async (decodedToken) => {
const key = await client.getSigningKeyAsync(decodedToken.header.kid) const key = await client.getSigningKeyAsync(decodedToken.header.kid)
return key.getPublicKey() return key.rsaPublicKey
}, }
}), })
) )
io.on("connection", async (socket) => { io.on('connection', async (socket) => {
// jwt payload of the connected client // jwt payload of the connected client
console.log(socket.decodedToken) console.log(socket.decodedToken)
// You can do the same things of the previous example there... // You can do the same things of the previous example there...
}) })
``` ```
### Server side with `onAuthentication` (example)
```ts
import { Server } from "socket.io"
import { authorize } from "@thream/socketio-jwt"
const io = new Server(9000)
io.use(
authorize({
secret: "your secret or public key",
onAuthentication: async (decodedToken) => {
// return the object that you want to add to the user property
// or throw an error if the token is unauthorized
},
}),
)
io.on("connection", async (socket) => {
// jwt payload of the connected client
console.log(socket.decodedToken)
// You can do the same things of the previous example there...
// user object returned in onAuthentication
console.log(socket.user)
})
```
### `authorize` options
- `secret` is a string containing the secret for HMAC algorithms, or a function that should fetch the secret or public key as shown in the example with `jwks-rsa`.
- `algorithms` (default: `HS256`)
- `onAuthentication` is a function that will be called with the `decodedToken` as a parameter after the token is authenticated. Return a value to add to the `user` property in the socket object.
### Client side ### Client side
```ts ```ts
import { io } from "socket.io-client" import { io } from 'socket.io-client'
import { isUnauthorizedError } from "@thream/socketio-jwt/build/UnauthorizedError.js"
// Require Bearer Token // Require Bearer Tokens to be passed in as an Authorization Header
const socket = io("http://localhost:9000", { const socket = io('http://localhost:9000', {
auth: { token: `Bearer ${yourJWT}` }, extraHeaders: { Authorization: `Bearer ${yourJWT}` }
}) })
// Handling token expiration // Handling token expiration
socket.on("connect_error", (error) => { socket.on('connect_error', (error) => {
if (isUnauthorizedError(error)) { if (error.data.type === 'UnauthorizedError') {
console.log("User token has expired") console.log('User token has expired')
} }
}) })
// Listening to events // Listening to events
socket.on("messages", (data) => { socket.on('messages', (data) => {
console.log(data) console.log(data)
}) })
``` ```
@ -159,7 +114,7 @@ socket.on("messages", (data) => {
Anyone can help to improve the project, submit a Feature Request, a bug report or even correct a simple spelling mistake. Anyone can help to improve the project, submit a Feature Request, a bug report or even correct a simple spelling mistake.
The steps to contribute can be found in the [CONTRIBUTING.md](./CONTRIBUTING.md) file. The steps to contribute can be found in the [CONTRIBUTING.md](./.github/CONTRIBUTING.md) file.
## 📄 License ## 📄 License

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }

14300
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +1,111 @@
{ {
"name": "@thream/socketio-jwt", "name": "@thream/socketio-jwt",
"version": "0.0.0-development", "version": "1.1.0",
"type": "module",
"public": true,
"description": "Authenticate socket.io incoming connections with JWTs.", "description": "Authenticate socket.io incoming connections with JWTs.",
"license": "MIT", "license": "MIT",
"main": "build/index.js", "main": "build/index.js",
"types": "build/index.d.ts", "types": "build/index.d.ts",
"files": [ "files": [
"build", "build"
"!**/*.test.js",
"!**/*.test.d.ts",
"!**/*.map"
], ],
"engines": { "engines": {
"node": ">=16.0.0", "node": ">=12"
"npm": ">=9.0.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}, },
"keywords": [ "keywords": [
"socket", "socket",
"socket.io", "socket.io",
"jwt" "jwt"
], ],
"author": "Théo LUDWIG <contact@theoludwig.fr>", "author": "Divlo <contact@divlo.fr>",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/Thream/socketio-jwt" "url": "git+https://github.com/Thream/socketio-jwt"
}, },
"bugs": { "bugs": {
"url": "https://github.com/Thream/socketio-jwt/issues" "url": "https://github.com/Thream/socketio-jwt/issues"
}, },
"homepage": "https://github.com/Thream/socketio-jwt#readme", "homepage": "https://github.com/Thream/socketio-jwt#readme",
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "npm run lint"
}
},
"release-it": {
"git": {
"commitMessage": "chore(release): v${version}"
},
"github": {
"release": false
},
"npm": {
"publish": false
},
"hooks": {
"before:init": [
"npm run lint",
"npm run test"
]
},
"plugins": {
"@release-it/conventional-changelog": {
"preset": "angular",
"infile": "CHANGELOG.md"
}
}
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"rootDir": "./src",
"collectCoverage": true,
"coverageDirectory": "../coverage/"
},
"ts-standard": {
"files": [
"./src/**/*.ts"
],
"envs": [
"node",
"jest"
]
},
"scripts": { "scripts": {
"build": "rimraf ./build && swc ./src --out-dir ./build", "build": "rimraf ./build && tsc",
"build:dev": "swc ./src --out-dir ./build --watch", "lint": "ts-standard | snazzy",
"build:typescript": "tsc", "format": "ts-standard --fix | snazzy",
"lint:commit": "commitlint", "release": "release-it",
"lint:editorconfig": "editorconfig-checker", "test": "jest",
"lint:markdown": "markdownlint-cli2", "test:watchAll": "jest --watchAll",
"lint:eslint": "eslint . --max-warnings 0 --report-unused-disable-directives --ignore-path .gitignore", "test:clearCache": "jest --clearCache"
"lint:prettier": "prettier . --check",
"lint:staged": "lint-staged",
"test": "cross-env NODE_ENV=test node --enable-source-maps --test build/",
"release": "semantic-release",
"postinstall": "husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable"
}, },
"peerDependencies": { "peerDependencies": {
"socket.io": ">=3.0.0" "socket.io": ">=3.0.0"
}, },
"dependencies": { "dependencies": {
"jsonwebtoken": "9.0.2" "jsonwebtoken": "8.5.1"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "18.0.0", "@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "18.0.0", "@commitlint/config-conventional": "11.0.0",
"@swc/cli": "0.1.62", "@release-it/conventional-changelog": "2.0.0",
"@swc/core": "1.3.94", "@types/express": "4.17.9",
"@tsconfig/strictest": "2.0.2", "@types/jest": "26.0.20",
"@types/jsonwebtoken": "9.0.4", "@types/jsonwebtoken": "8.5.0",
"@types/node": "20.8.7", "@types/node": "14.14.20",
"@typescript-eslint/eslint-plugin": "6.9.0", "@types/server-destroy": "1.0.1",
"@typescript-eslint/parser": "6.9.0", "axios": "0.21.1",
"axios": "1.5.1", "express": "4.17.1",
"cross-env": "7.0.3", "husky": "4.3.7",
"editorconfig-checker": "5.1.1", "jest": "26.6.3",
"eslint": "8.52.0", "release-it": "14.2.2",
"eslint-config-conventions": "12.0.0", "rimraf": "3.0.2",
"eslint-config-prettier": "9.0.0", "server-destroy": "1.0.1",
"eslint-plugin-import": "2.29.0", "snazzy": "9.0.0",
"eslint-plugin-prettier": "5.0.1", "socket.io": "3.0.5",
"eslint-plugin-promise": "6.1.1", "socket.io-client": "3.0.5",
"eslint-plugin-unicorn": "48.0.1", "ts-jest": "26.4.4",
"fastify": "4.24.3", "ts-standard": "10.0.0",
"husky": "8.0.3", "typescript": "4.1.3"
"lint-staged": "15.0.2",
"markdownlint-cli2": "0.10.0",
"markdownlint-rule-relative-links": "2.1.0",
"pinst": "3.0.0",
"prettier": "3.0.3",
"rimraf": "5.0.5",
"semantic-release": "22.0.5",
"socket.io": "4.7.2",
"socket.io-client": "4.7.2",
"typescript": "5.2.2"
} }
} }

View File

@ -1,30 +1,16 @@
export class UnauthorizedError extends Error { export class UnauthorizedError extends Error {
public inner: { message: string } public inner: { message: string }
public data: { message: string; code: string; type: "UnauthorizedError" } public data: { message: string, code: string, type: 'UnauthorizedError' }
constructor (code: string, error: { message: string }) { constructor (code: string, error: { message: string }) {
super(error.message) super(error.message)
this.name = "UnauthorizedError" this.message = error.message
this.inner = error this.inner = error
this.data = { this.data = {
message: this.message, message: this.message,
code, code,
type: "UnauthorizedError", type: 'UnauthorizedError'
} }
Object.setPrototypeOf(this, UnauthorizedError.prototype) Object.setPrototypeOf(this, UnauthorizedError.prototype)
} }
} }
export const isUnauthorizedError = (
error: unknown,
): error is UnauthorizedError => {
return (
typeof error === "object" &&
error != null &&
"data" in error &&
typeof error.data === "object" &&
error.data != null &&
"type" in error.data &&
error.data.type === "UnauthorizedError"
)
}

View File

@ -1,340 +1,103 @@
import test from "node:test" import axios from 'axios'
import assert from "node:assert/strict" import { io } from 'socket.io-client'
import axios from "axios" import { fixtureStart, fixtureStop } from './fixture'
import type { Socket } from "socket.io-client"
import { io } from "socket.io-client"
import { isUnauthorizedError } from "../UnauthorizedError.js" describe('authorize - with secret as string in options', () => {
import type { Profile } from "./fixture/index.js" let token: string = ''
import {
API_URL,
fixtureStart,
fixtureStop,
getSocket,
basicProfile,
} from "./fixture/index.js"
export const api = axios.create({ beforeEach(async (done) => {
baseURL: API_URL, jest.setTimeout(15_000)
headers: { await fixtureStart(async () => {
"Content-Type": "application/json", const response = await axios.post('http://localhost:9000/login')
}, token = response.data.token
done()
})
})
afterEach((done) => {
fixtureStop(done)
})
it('should emit error with no token provided', (done) => {
const socket = io('http://localhost:9000')
socket.on('connect_error', (err: any) => {
expect(err.data.message).toEqual('no token provided')
expect(err.data.code).toEqual('credentials_required')
socket.close()
done()
})
})
it('should emit error with bad token format', (done) => {
const socket = io('http://localhost:9000', {
extraHeaders: { Authorization: 'testing' }
})
socket.on('connect_error', (err: any) => {
expect(err.data.message).toEqual(
'Format is Authorization: Bearer [token]'
)
expect(err.data.code).toEqual('credentials_bad_format')
socket.close()
done()
})
})
it('should emit error with unauthorized handshake', (done) => {
const socket = io('http://localhost:9000', {
extraHeaders: { Authorization: 'Bearer testing' }
})
socket.on('connect_error', (err: any) => {
expect(err.data.message).toEqual(
'Unauthorized: Token is missing or invalid Bearer'
)
expect(err.data.code).toEqual('invalid_token')
socket.close()
done()
})
})
it('should connect the user', (done) => {
const socket = io('http://localhost:9000', {
extraHeaders: { Authorization: `Bearer ${token}` }
})
socket.on('connect', () => {
socket.close()
done()
})
})
}) })
const secretCallback = async (): Promise<string> => { const secretCallback = async (): Promise<string> => {
return "somesecret" return 'somesecret'
} }
await test("authorize", async (t) => { describe('authorize - with secret as callback in options', () => {
await t.test("with secret as string in options", async (t) => { let token: string = ''
let token = ""
let socket: Socket | null = null
t.beforeEach(async () => { beforeEach(async (done) => {
await fixtureStart() jest.setTimeout(15_000)
const response = await api.post("/login", {}) await fixtureStart(
async () => {
const response = await axios.post('http://localhost:9000/login')
token = response.data.token token = response.data.token
}) done()
t.afterEach(async () => {
socket?.disconnect()
await fixtureStop()
})
await t.test("should emit error with no token provided", () => {
socket = io(API_URL)
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(error.data.message, "no token provided")
assert.strictEqual(error.data.code, "credentials_required")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should emit error with bad token format", () => {
socket = io(API_URL, {
auth: { token: "testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Format is Authorization: Bearer [token]",
)
assert.strictEqual(error.data.code, "credentials_bad_format")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should emit error with unauthorized handshake", () => {
socket = io(API_URL, {
auth: { token: "Bearer testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Unauthorized: Token is missing or invalid Bearer",
)
assert.strictEqual(error.data.code, "invalid_token")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should connect the user", () => {
socket = io(API_URL, {
auth: { token: `Bearer ${token}` },
})
socket.on("connect", async () => {
assert.ok(true)
})
socket.on("connect_error", async (error) => {
assert.fail(error.message)
})
})
})
await t.test("with secret as callback in options", async (t) => {
let token = ""
let socket: Socket | null = null
t.beforeEach(async () => {
await fixtureStart({ secret: secretCallback })
const response = await api.post("/login", {})
token = response.data.token
})
t.afterEach(async () => {
socket?.disconnect()
await fixtureStop()
})
await t.test("should emit error with no token provided", () => {
socket = io(API_URL)
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(error.data.message, "no token provided")
assert.strictEqual(error.data.code, "credentials_required")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should emit error with bad token format", () => {
socket = io(API_URL, {
auth: { token: "testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Format is Authorization: Bearer [token]",
)
assert.strictEqual(error.data.code, "credentials_bad_format")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should emit error with unauthorized handshake", () => {
socket = io(API_URL, {
auth: { token: "Bearer testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Unauthorized: Token is missing or invalid Bearer",
)
assert.strictEqual(error.data.code, "invalid_token")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should connect the user", () => {
socket = io(API_URL, {
auth: { token: `Bearer ${token}` },
})
socket.on("connect", async () => {
assert.ok(true)
})
socket.on("connect_error", async (error) => {
assert.fail(error.message)
})
})
})
await t.test("with onAuthentication callback in options", async (t) => {
let token = ""
let wrongToken = ""
let socket: Socket | null = null
t.beforeEach(async () => {
await fixtureStart({
secret: secretCallback,
onAuthentication: (decodedToken: Profile) => {
if (!decodedToken.checkField) {
throw new Error("Check Field validation failed")
}
return {
email: decodedToken.email,
}
}, },
}) { secret: secretCallback }
const response = await api.post("/login", {})
token = response.data.token
const responseWrong = await api.post("/login-wrong", {})
wrongToken = responseWrong.data.token
})
t.afterEach(async () => {
socket?.disconnect()
await fixtureStop()
})
await t.test("should emit error with no token provided", () => {
socket = io(API_URL)
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(error.data.message, "no token provided")
assert.strictEqual(error.data.code, "credentials_required")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
})
await t.test("should emit error with bad token format", () => {
socket = io(API_URL, {
auth: { token: "testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Format is Authorization: Bearer [token]",
) )
assert.strictEqual(error.data.code, "credentials_bad_format")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
}) })
await t.test("should emit error with unauthorized handshake", () => { afterEach((done) => {
socket = io(API_URL, { fixtureStop(done)
auth: { token: "Bearer testing" },
})
socket.on("connect_error", async (error) => {
assert.strictEqual(isUnauthorizedError(error), true)
if (isUnauthorizedError(error)) {
assert.strictEqual(
error.data.message,
"Unauthorized: Token is missing or invalid Bearer",
)
assert.strictEqual(error.data.code, "invalid_token")
assert.ok(true)
} else {
assert.fail("should be unauthorized error")
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
})
}) })
await t.test("should connect the user", () => { it('should connect the user', (done) => {
socket = io(API_URL, { const socket = io('http://localhost:9000', {
auth: { token: `Bearer ${token}` }, extraHeaders: { Authorization: `Bearer ${token}` }
})
socket.on("connect", async () => {
assert.ok(true)
})
socket.on("connect_error", async (error) => {
assert.fail(error.message)
})
})
await t.test("should contains user properties", () => {
const socketServer = getSocket()
socketServer?.on("connection", (client: any) => {
assert.strictEqual(client.user.email, basicProfile.email)
assert.ok(true)
})
socket = io(API_URL, {
auth: { token: `Bearer ${token}` },
})
socket.on("connect_error", async (error) => {
assert.fail(error.message)
})
})
await t.test("should emit error when user validation fails", () => {
socket = io(API_URL, {
auth: { token: `Bearer ${wrongToken}` },
})
socket.on("connect_error", async (error) => {
try {
assert.strictEqual(error.message, "Check Field validation failed")
assert.ok(true)
} catch {
assert.fail(error.message)
}
})
socket.on("connect", async () => {
assert.fail("should not connect")
}) })
socket.on('connect', () => {
socket.close()
done()
}) })
}) })
}) })

View File

@ -1,83 +1,58 @@
import jwt from "jsonwebtoken" import express from 'express'
import { Server as SocketIoServer } from "socket.io" import jwt from 'jsonwebtoken'
import type { FastifyInstance } from "fastify" import { Server as HttpServer } from 'http'
import fastify from "fastify" import { Server as HttpsServer } from 'https'
import { Server as SocketIoServer } from 'socket.io'
import enableDestroy from 'server-destroy'
import type { AuthorizeOptions } from "../../index.js" import { authorize, AuthorizeOptions } from '../../index'
import { authorize } from "../../index.js"
interface FastifyIo { interface Socket {
instance: SocketIoServer io: null | SocketIoServer
init: (httpServer: HttpServer | HttpsServer) => void
} }
declare module "fastify" { const socket: Socket = {
export interface FastifyInstance { io: null,
io: FastifyIo init (httpServer) {
socket.io = new SocketIoServer(httpServer)
} }
} }
export interface BasicProfile { let server: HttpServer | null = null
email: string
id: number
}
export interface Profile extends BasicProfile {
checkField: boolean
}
export const PORT = 9000
export const API_URL = `http://localhost:${PORT}`
export const basicProfile: BasicProfile = {
email: "john@doe.com",
id: 123,
}
let application: FastifyInstance | null = null
export const fixtureStart = async ( export const fixtureStart = async (
options: AuthorizeOptions = { secret: "super secret" }, done: any,
options: AuthorizeOptions = { secret: 'aaafoo super sercret' }
): Promise<void> => { ): Promise<void> => {
const profile: Profile = { ...basicProfile, checkField: true } const app = express()
let keySecret = "" app.use(express.json())
if (typeof options.secret === "string") { let keySecret = 'secret'
if (typeof options.secret === 'string') {
keySecret = options.secret keySecret = options.secret
} else { } else {
keySecret = await options.secret({ keySecret = await options.secret(() => {})
header: { alg: "HS256" }, }
payload: profile, app.post('/login', (_req, res) => {
}) const profile = {
email: 'john@doe.com',
id: 123
} }
application = fastify()
application.post("/login", async (_request, reply) => {
const token = jwt.sign(profile, keySecret, { const token = jwt.sign(profile, keySecret, {
expiresIn: 60 * 60 * 5, expiresIn: 60 * 60 * 5
}) })
reply.statusCode = 201 return res.json({ token })
return { token }
})
application.post("/login-wrong", async (_request, reply) => {
profile.checkField = false
const token = jwt.sign(profile, keySecret, {
expiresIn: 60 * 60 * 5,
})
reply.statusCode = 201
return { token }
})
const instance = new SocketIoServer(application.server)
instance.use(authorize(options))
application.decorate("io", { instance })
application.addHook("onClose", (fastify) => {
fastify.io.instance.close()
})
await application.listen({
port: PORT,
}) })
server = app.listen(9000, done)
socket.init(server)
socket.io?.use(authorize(options))
enableDestroy(server)
} }
export const fixtureStop = async (): Promise<void> => { export const fixtureStop = (callback: Function): void => {
await application?.close() socket.io?.close()
} try {
server?.destroy()
export const getSocket = (): SocketIoServer | undefined => { } catch (err) {}
return application?.io.instance callback()
} }

View File

@ -1,91 +1,77 @@
import type { Algorithm } from "jsonwebtoken" import jwt, { Algorithm } from 'jsonwebtoken'
import jwt from "jsonwebtoken" import { Socket } from 'socket.io'
import type { Socket } from "socket.io"
import { UnauthorizedError } from "./UnauthorizedError.js" import { UnauthorizedError } from './UnauthorizedError'
declare module "socket.io" { declare module 'socket.io' {
interface Socket extends ExtendedSocket {} interface Socket extends ExtendedSocket {}
} }
interface ExtendedError extends Error {
data?: any
}
interface ExtendedSocket { interface ExtendedSocket {
encodedToken?: string encodedToken?: string
decodedToken?: any decodedToken?: any
user?: any
} }
type SocketIOMiddleware = ( type SocketIOMiddleware = (
socket: Socket, socket: Socket,
next: (error?: UnauthorizedError) => void, next: (err?: ExtendedError) => void
) => void ) => void
interface CompleteDecodedToken { type SecretCallback = (decodedToken: null | { [key: string]: any } | string) => Promise<string>
header: {
alg: Algorithm
[key: string]: any
}
payload: any
}
type SecretCallback = (
decodedToken: CompleteDecodedToken,
) => Promise<string> | string
export interface AuthorizeOptions { export interface AuthorizeOptions {
secret: string | SecretCallback secret: string | SecretCallback
algorithms?: Algorithm[] algorithms?: Algorithm[]
onAuthentication?: (decodedToken: any) => Promise<any> | any
} }
export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => { export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
const { secret, algorithms = ["HS256"], onAuthentication } = options const { secret, algorithms = ['HS256'] } = options
return async (socket, next) => { return async (socket, next) => {
let encodedToken: string | null = null let encodedToken: string | null = null
const { token } = socket.handshake.auth const authorizationHeader = socket.request.headers.authorization
if (token != null) { if (authorizationHeader != null) {
const tokenSplitted = token.split(" ") const tokenSplitted = authorizationHeader.split(' ')
if (tokenSplitted.length !== 2 || tokenSplitted[0] !== "Bearer") { if (tokenSplitted.length !== 2 || tokenSplitted[0] !== 'Bearer') {
return next( return next(
new UnauthorizedError("credentials_bad_format", { new UnauthorizedError('credentials_bad_format', {
message: "Format is Authorization: Bearer [token]", message: 'Format is Authorization: Bearer [token]'
}), })
) )
} }
encodedToken = tokenSplitted[1] encodedToken = tokenSplitted[1]
} }
if (encodedToken == null) { if (encodedToken == null) {
return next( return next(
new UnauthorizedError("credentials_required", { new UnauthorizedError('credentials_required', {
message: "no token provided", message: 'no token provided'
}), })
) )
} }
// Store encoded JWT
socket.encodedToken = encodedToken socket.encodedToken = encodedToken
let keySecret: string | null = null let keySecret: string | null = null
let decodedToken: any = null let decodedToken: any
if (typeof secret === "string") { if (typeof secret === 'string') {
keySecret = secret keySecret = secret
} else { } else {
const completeDecodedToken = jwt.decode(encodedToken, { complete: true }) decodedToken = jwt.decode(encodedToken, { complete: true })
keySecret = await secret(completeDecodedToken as CompleteDecodedToken) keySecret = await secret(decodedToken)
} }
try { try {
decodedToken = jwt.verify(encodedToken, keySecret, { algorithms }) decodedToken = jwt.verify(encodedToken, keySecret, { algorithms })
} catch { } catch {
return next( return next(
new UnauthorizedError("invalid_token", { new UnauthorizedError('invalid_token', {
message: "Unauthorized: Token is missing or invalid Bearer", message: 'Unauthorized: Token is missing or invalid Bearer'
}), })
) )
} }
// Store decoded JWT
socket.decodedToken = decodedToken socket.decodedToken = decodedToken
if (onAuthentication != null) {
try {
socket.user = await onAuthentication(decodedToken)
} catch (error: any) {
return next(error)
}
}
return next() return next()
} }
} }

View File

@ -1,2 +1 @@
export * from "./authorize.js" export * from './authorize'
export * from "./UnauthorizedError.js"

View File

@ -1,13 +1,23 @@
{ {
"extends": "@tsconfig/strictest/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ES2019",
"lib": ["ESNext"], "module": "commonjs",
"module": "NodeNext", "lib": ["ES2019"],
"moduleResolution": "NodeNext", "moduleResolution": "node",
"allowJs": false,
"checkJs": false,
"declaration": true,
"sourceMap": false,
"outDir": "./build", "outDir": "./build",
"rootDir": "./src", "rootDir": "./src",
"emitDeclarationOnly": true, "removeComments": false,
"declaration": true "noEmitOnError": true,
"importHelpers": false,
"strict": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"forceConsistentCasingInFileNames": true,
"incremental": false
} }
} }