17 Commits

Author SHA1 Message Date
fd79d99235 chore(release): v1.1.0 2021-01-07 14:32:55 +01:00
261e8d66e2 feat: add support for jwks-rsa (#1) 2021-01-07 14:30:37 +01:00
ca83ad4ba2 build(deps-dev): bump husky from 4.3.6 to 4.3.7 (#11) 2021-01-07 11:47:55 +01:00
6d0f23ef31 chore: fix package.json syntax error 2021-01-07 05:45:46 +01:00
8c8d38fd69 build(deps-dev): bump socket.io-client from 3.0.4 to 3.0.5 (#9) 2021-01-06 19:07:15 +01:00
544801d8ba build(deps-dev): bump socket.io from 3.0.4 to 3.0.5 (#8) 2021-01-06 19:06:38 +01:00
0c68ada2b1 build(deps-dev): bump @types/node from 14.14.19 to 14.14.20 (#7) 2021-01-05 08:32:12 +01:00
b0c0cf6ee0 build(deps-dev): bump @types/node from 14.14.17 to 14.14.19 (#5) 2021-01-04 14:36:25 +01:00
84b523f434 feat: improve types by extending socket.io module (#6) 2021-01-04 14:35:59 +01:00
abc1225189 docs: update code of conduct link 2021-01-02 18:35:26 +01:00
e87a335064 chore: ts-standard linting in scripts 2021-01-01 04:42:06 +01:00
89bfd83cfc build(deps-dev): bump @types/node from 14.14.16 to 14.14.17 (#2) 2020-12-31 13:11:15 +01:00
abbabc588e feat: add algorithms option 2020-12-30 14:50:56 +01:00
92d1ecd7e0 chore(release): v1.0.1 2020-12-29 12:32:10 +01:00
0e3055a6b5 docs(readme): add npm badge version 2020-12-29 12:28:53 +01:00
5f3e5ceb37 ci: faster build by removing codeql_analysis 2020-12-29 12:23:18 +01:00
68724248eb docs(readme): fix socketioJWT import to authorize 2020-12-29 12:22:23 +01:00
9 changed files with 150 additions and 209 deletions

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@divlo.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

@ -4,7 +4,7 @@ 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.
**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

View File

@ -1,41 +0,0 @@
# For more information see: https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code
name: 'CodeQL'
on:
push:
branches: [master, develop]
pull_request_review:
branches: [master, develop]
types: [submitted]
jobs:
analyze:
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') }}
name: 'Analyze'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: ['javascript']
steps:
- name: 'Checkout repository'
uses: actions/checkout@v2
with:
fetch-depth: 2
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
- name: 'Initialize CodeQL'
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
- name: 'Autobuild'
uses: github/codeql-action/autobuild@v1
- name: 'Perform CodeQL Analysis'
uses: github/codeql-action/analyze@v1

View File

@ -1,3 +1,19 @@
# [1.0.0](https://github.com/Thream/socketio-jwt/compare/v4.6.2...v1.0.0) (2020-12-29)
# 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

@ -8,17 +8,18 @@
<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>
<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>
<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="./.github/CODE_OF_CONDUCT.md"><img src="https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg" alt="Contributor Covenant" /></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>
## 📜 About
Authenticate socket.io incoming connections with JWTs.
Compatible with `socket.io >= 3.0`.
Compatible with `socket.io >= 3.0.0`.
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.
@ -34,7 +35,7 @@ npm install --save @thream/socketio-jwt
```ts
import { Server } from 'socket.io'
import socketioJWT from '@thream/socketio-jwt'
import { authorize } from '@thream/socketio-jwt'
const io = new Server(9000)
io.use(
@ -43,15 +44,49 @@ io.use(
})
)
io.on('connection', async () => {
io.on('connection', async (socket) => {
// jwt payload of the connected client
console.log(socket.decodedToken)
const clients = await io.sockets.allSockets()
for (const clientId of clients) {
const client = io.sockets.sockets.get(clientId)
console.log(client.decodedToken) // we can access the jwt payload of each connected client
if (clients != null) {
for (const clientId of clients) {
const client = io.sockets.sockets.get(clientId)
client?.emit('messages', { message: 'Success!' })
// we can access the jwt payload of each connected client
console.log(client?.decodedToken)
}
}
})
```
### Server side with `jwks-rsa` (example)
```ts
import jwksClient from 'jwks-rsa'
import { Server } from 'socket.io'
import { authorize } from '@thream/socketio-jwt'
const client = jwksClient({
jwksUri: 'https://sandrino.auth0.com/.well-known/jwks.json'
})
const io = new Server(9000)
io.use(
authorize({
secret: async (decodedToken) => {
const key = await client.getSigningKeyAsync(decodedToken.header.kid)
return key.rsaPublicKey
}
})
)
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...
})
```
### Client side
```ts

View File

@ -1,6 +1,6 @@
{
"name": "@thream/socketio-jwt",
"version": "1.0.0",
"version": "1.1.0",
"description": "Authenticate socket.io incoming connections with JWTs.",
"license": "MIT",
"main": "build/index.js",
@ -72,7 +72,7 @@
},
"scripts": {
"build": "rimraf ./build && tsc",
"lint": "exit 0",
"lint": "ts-standard | snazzy",
"format": "ts-standard --fix | snazzy",
"release": "release-it",
"test": "jest",
@ -80,7 +80,7 @@
"test:clearCache": "jest --clearCache"
},
"peerDependencies": {
"socket.io": "*"
"socket.io": ">=3.0.0"
},
"dependencies": {
"jsonwebtoken": "8.5.1"
@ -90,20 +90,20 @@
"@commitlint/config-conventional": "11.0.0",
"@release-it/conventional-changelog": "2.0.0",
"@types/express": "4.17.9",
"@types/jest": "26.0.19",
"@types/jest": "26.0.20",
"@types/jsonwebtoken": "8.5.0",
"@types/node": "14.14.16",
"@types/node": "14.14.20",
"@types/server-destroy": "1.0.1",
"axios": "0.21.1",
"express": "4.17.1",
"husky": "4.3.6",
"husky": "4.3.7",
"jest": "26.6.3",
"release-it": "14.2.2",
"rimraf": "3.0.2",
"server-destroy": "1.0.1",
"snazzy": "9.0.0",
"socket.io": "3.0.4",
"socket.io-client": "3.0.4",
"socket.io": "3.0.5",
"socket.io-client": "3.0.5",
"ts-jest": "26.4.4",
"ts-standard": "10.0.0",
"typescript": "4.1.3"

View File

@ -3,12 +3,12 @@ import { io } from 'socket.io-client'
import { fixtureStart, fixtureStop } from './fixture'
describe('authorize', () => {
describe('authorize - with secret as string in options', () => {
let token: string = ''
beforeEach((done) => {
beforeEach(async (done) => {
jest.setTimeout(15_000)
fixtureStart(async () => {
await fixtureStart(async () => {
const response = await axios.post('http://localhost:9000/login')
token = response.data.token
done()
@ -67,3 +67,37 @@ describe('authorize', () => {
})
})
})
const secretCallback = async (): Promise<string> => {
return 'somesecret'
}
describe('authorize - with secret as callback in options', () => {
let token: string = ''
beforeEach(async (done) => {
jest.setTimeout(15_000)
await fixtureStart(
async () => {
const response = await axios.post('http://localhost:9000/login')
token = response.data.token
done()
},
{ secret: secretCallback }
)
})
afterEach((done) => {
fixtureStop(done)
})
it('should connect the user', (done) => {
const socket = io('http://localhost:9000', {
extraHeaders: { Authorization: `Bearer ${token}` }
})
socket.on('connect', () => {
socket.close()
done()
})
})
})

View File

@ -5,7 +5,7 @@ import { Server as HttpsServer } from 'https'
import { Server as SocketIoServer } from 'socket.io'
import enableDestroy from 'server-destroy'
import { authorize } from '../../index'
import { authorize, AuthorizeOptions } from '../../index'
interface Socket {
io: null | SocketIoServer
@ -21,16 +21,26 @@ const socket: Socket = {
let server: HttpServer | null = null
export const fixtureStart = (done: any): void => {
const options = { secret: 'aaafoo super sercret' }
export const fixtureStart = async (
done: any,
options: AuthorizeOptions = { secret: 'aaafoo super sercret' }
): Promise<void> => {
const app = express()
app.use(express.json())
let keySecret = 'secret'
if (typeof options.secret === 'string') {
keySecret = options.secret
} else {
keySecret = await options.secret(() => {})
}
app.post('/login', (_req, res) => {
const profile = {
email: 'john@doe.com',
id: 123
}
const token = jwt.sign(profile, options.secret, { expiresIn: 60 * 60 * 5 })
const token = jwt.sign(profile, keySecret, {
expiresIn: 60 * 60 * 5
})
return res.json({ token })
})
server = app.listen(9000, done)

View File

@ -1,25 +1,37 @@
import jwt from 'jsonwebtoken'
import jwt, { Algorithm } from 'jsonwebtoken'
import { Socket } from 'socket.io'
import { UnauthorizedError } from './UnauthorizedError'
declare module 'socket.io' {
interface Socket extends ExtendedSocket {}
}
interface ExtendedError extends Error {
data?: any
}
interface ExtendedSocket {
encodedToken?: string
decodedToken?: any
}
type SocketIOMiddleware = (
socket: Socket,
next: (err?: ExtendedError) => void
) => void
interface AuthorizeOptions {
secret: string
type SecretCallback = (decodedToken: null | { [key: string]: any } | string) => Promise<string>
export interface AuthorizeOptions {
secret: string | SecretCallback
algorithms?: Algorithm[]
}
export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
const { secret } = options
return (socket, next) => {
let token: string | null = null
const { secret, algorithms = ['HS256'] } = options
return async (socket, next) => {
let encodedToken: string | null = null
const authorizationHeader = socket.request.headers.authorization
if (authorizationHeader != null) {
const tokenSplitted = authorizationHeader.split(' ')
@ -30,9 +42,9 @@ export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
})
)
}
token = tokenSplitted[1]
encodedToken = tokenSplitted[1]
}
if (token == null) {
if (encodedToken == null) {
return next(
new UnauthorizedError('credentials_required', {
message: 'no token provided'
@ -40,10 +52,17 @@ export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
)
}
// Store encoded JWT
socket = Object.assign(socket, { encodedToken: token })
let payload: any
socket.encodedToken = encodedToken
let keySecret: string | null = null
let decodedToken: any
if (typeof secret === 'string') {
keySecret = secret
} else {
decodedToken = jwt.decode(encodedToken, { complete: true })
keySecret = await secret(decodedToken)
}
try {
payload = jwt.verify(token, secret)
decodedToken = jwt.verify(encodedToken, keySecret, { algorithms })
} catch {
return next(
new UnauthorizedError('invalid_token', {
@ -52,7 +71,7 @@ export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
)
}
// Store decoded JWT
socket = Object.assign(socket, { decodedToken: payload })
socket.decodedToken = decodedToken
return next()
}
}