2
1
mirror of https://github.com/Thream/socketio-jwt.git synced 2024-07-06 19:30:11 +02:00

chore: better Prettier config for easier reviews

This commit is contained in:
Théo LUDWIG 2023-10-23 23:44:50 +02:00
parent 2d84d11034
commit 5f2742be0b
Signed by: theoludwig
GPG Key ID: ADFE5A563D718F3B
18 changed files with 1256 additions and 1256 deletions

View File

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

View File

@ -1,8 +1,8 @@
--- ---
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]' 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. -->

View File

@ -1,8 +1,8 @@
--- ---
name: '✨ Feature Request' name: "✨ Feature Request"
about: 'Suggest a new feature idea.' about: "Suggest a new feature idea."
title: '[Feature]' 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. -->

View File

@ -1,8 +1,8 @@
--- ---
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]' 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. -->

View File

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

View File

@ -1,4 +1,4 @@
name: 'Build' name: "Build"
on: on:
push: push:
@ -8,20 +8,20 @@ on:
jobs: jobs:
build: build:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: '20.x' node-version: "20.x"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- name: 'Build' - name: "Build"
run: 'npm run build' run: "npm run build"
- run: 'npm run build:typescript' - run: "npm run build:typescript"

View File

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

View File

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

View File

@ -1,4 +1,4 @@
name: 'Test' name: "Test"
on: on:
push: push:
@ -8,21 +8,21 @@ on:
jobs: jobs:
test: test:
runs-on: 'ubuntu-latest' runs-on: "ubuntu-latest"
steps: steps:
- uses: 'actions/checkout@v4.0.0' - uses: "actions/checkout@v4.0.0"
- name: 'Setup Node.js' - name: "Setup Node.js"
uses: 'actions/setup-node@v3.8.1' uses: "actions/setup-node@v3.8.1"
with: with:
node-version: '20.x' node-version: "20.x"
cache: 'npm' cache: "npm"
- name: 'Install dependencies' - name: "Install dependencies"
run: 'npm clean-install' run: "npm clean-install"
- name: 'Build' - name: "Build"
run: 'npm run build' run: "npm run build"
- name: 'Test' - name: "Test"
run: 'npm run test' run: "npm run test"

View File

@ -1,6 +1,3 @@
{ {
"singleQuote": true, "semi": false
"jsxSingleQuote": true,
"semi": false,
"trailingComma": "none"
} }

View File

@ -42,24 +42,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)
} }
@ -70,12 +70,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)
@ -84,11 +84,11 @@ io.use(
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.getPublicKey()
} },
}) }),
) )
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...
@ -98,21 +98,21 @@ io.on('connection', async (socket) => {
### Server side with `onAuthentication` (example) ### Server side with `onAuthentication` (example)
```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",
onAuthentication: async (decodedToken) => { onAuthentication: async (decodedToken) => {
// return the object that you want to add to the user property // return the object that you want to add to the user property
// or throw an error if the token is unauthorized // or throw an error if the token is unauthorized
} },
}) }),
) )
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...
@ -130,23 +130,23 @@ io.on('connection', async (socket) => {
### 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' import { isUnauthorizedError } from "@thream/socketio-jwt/build/UnauthorizedError.js"
// Require Bearer Token // Require Bearer Token
const socket = io('http://localhost:9000', { const socket = io("http://localhost:9000", {
auth: { token: `Bearer ${yourJWT}` } auth: { token: `Bearer ${yourJWT}` },
}) })
// Handling token expiration // Handling token expiration
socket.on('connect_error', (error) => { socket.on("connect_error", (error) => {
if (isUnauthorizedError(error)) { if (isUnauthorizedError(error)) {
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)
}) })
``` ```

1927
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -58,34 +58,34 @@
"jsonwebtoken": "9.0.2" "jsonwebtoken": "9.0.2"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "17.7.1", "@commitlint/cli": "18.0.0",
"@commitlint/config-conventional": "17.7.0", "@commitlint/config-conventional": "18.0.0",
"@swc/cli": "0.1.62", "@swc/cli": "0.1.62",
"@swc/core": "1.3.85", "@swc/core": "1.3.94",
"@tsconfig/strictest": "2.0.2", "@tsconfig/strictest": "2.0.2",
"@types/jsonwebtoken": "9.0.3", "@types/jsonwebtoken": "9.0.4",
"@types/node": "20.6.2", "@types/node": "20.8.7",
"@typescript-eslint/eslint-plugin": "6.7.2", "@typescript-eslint/eslint-plugin": "6.9.0",
"@typescript-eslint/parser": "6.7.2", "@typescript-eslint/parser": "6.9.0",
"axios": "1.5.0", "axios": "1.5.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"editorconfig-checker": "5.1.1", "editorconfig-checker": "5.1.1",
"eslint": "8.49.0", "eslint": "8.52.0",
"eslint-config-conventions": "11.0.1", "eslint-config-conventions": "12.0.0",
"eslint-config-prettier": "9.0.0", "eslint-config-prettier": "9.0.0",
"eslint-plugin-import": "2.28.1", "eslint-plugin-import": "2.29.0",
"eslint-plugin-prettier": "5.0.0", "eslint-plugin-prettier": "5.0.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "48.0.1", "eslint-plugin-unicorn": "48.0.1",
"fastify": "4.23.2", "fastify": "4.24.3",
"husky": "8.0.3", "husky": "8.0.3",
"lint-staged": "14.0.1", "lint-staged": "15.0.2",
"markdownlint-cli2": "0.10.0", "markdownlint-cli2": "0.10.0",
"markdownlint-rule-relative-links": "2.1.0", "markdownlint-rule-relative-links": "2.1.0",
"pinst": "3.0.0", "pinst": "3.0.0",
"prettier": "3.0.3", "prettier": "3.0.3",
"rimraf": "5.0.1", "rimraf": "5.0.5",
"semantic-release": "22.0.0", "semantic-release": "22.0.5",
"socket.io": "4.7.2", "socket.io": "4.7.2",
"socket.io-client": "4.7.2", "socket.io-client": "4.7.2",
"typescript": "5.2.2" "typescript": "5.2.2"

View File

@ -1,30 +1,30 @@
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.name = "UnauthorizedError"
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 = ( export const isUnauthorizedError = (
error: unknown error: unknown,
): error is UnauthorizedError => { ): error is UnauthorizedError => {
return ( return (
typeof error === 'object' && typeof error === "object" &&
error != null && error != null &&
'data' in error && "data" in error &&
typeof error.data === 'object' && typeof error.data === "object" &&
error.data != null && error.data != null &&
'type' in error.data && "type" in error.data &&
error.data.type === 'UnauthorizedError' error.data.type === "UnauthorizedError"
) )
} }

View File

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

View File

@ -1,16 +1,16 @@
import jwt from 'jsonwebtoken' import jwt from "jsonwebtoken"
import { Server as SocketIoServer } from 'socket.io' import { Server as SocketIoServer } from "socket.io"
import type { FastifyInstance } from 'fastify' import type { FastifyInstance } from "fastify"
import fastify from 'fastify' import fastify from "fastify"
import type { AuthorizeOptions } from '../../index.js' import type { AuthorizeOptions } from "../../index.js"
import { authorize } from '../../index.js' import { authorize } from "../../index.js"
interface FastifyIo { interface FastifyIo {
instance: SocketIoServer instance: SocketIoServer
} }
declare module 'fastify' { declare module "fastify" {
export interface FastifyInstance { export interface FastifyInstance {
io: FastifyIo io: FastifyIo
} }
@ -28,49 +28,49 @@ export interface Profile extends BasicProfile {
export const PORT = 9000 export const PORT = 9000
export const API_URL = `http://localhost:${PORT}` export const API_URL = `http://localhost:${PORT}`
export const basicProfile: BasicProfile = { export const basicProfile: BasicProfile = {
email: 'john@doe.com', email: "john@doe.com",
id: 123 id: 123,
} }
let application: FastifyInstance | null = null let application: FastifyInstance | null = null
export const fixtureStart = async ( export const fixtureStart = async (
options: AuthorizeOptions = { secret: 'super secret' } options: AuthorizeOptions = { secret: "super secret" },
): Promise<void> => { ): Promise<void> => {
const profile: Profile = { ...basicProfile, checkField: true } const profile: Profile = { ...basicProfile, checkField: true }
let keySecret = '' let keySecret = ""
if (typeof options.secret === 'string') { if (typeof options.secret === "string") {
keySecret = options.secret keySecret = options.secret
} else { } else {
keySecret = await options.secret({ keySecret = await options.secret({
header: { alg: 'HS256' }, header: { alg: "HS256" },
payload: profile payload: profile,
}) })
} }
application = fastify() application = fastify()
application.post('/login', async (_request, reply) => { 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 reply.statusCode = 201
return { token } return { token }
}) })
application.post('/login-wrong', async (_request, reply) => { application.post("/login-wrong", async (_request, reply) => {
profile.checkField = false profile.checkField = false
const token = jwt.sign(profile, keySecret, { const token = jwt.sign(profile, keySecret, {
expiresIn: 60 * 60 * 5 expiresIn: 60 * 60 * 5,
}) })
reply.statusCode = 201 reply.statusCode = 201
return { token } return { token }
}) })
const instance = new SocketIoServer(application.server) const instance = new SocketIoServer(application.server)
instance.use(authorize(options)) instance.use(authorize(options))
application.decorate('io', { instance }) application.decorate("io", { instance })
application.addHook('onClose', (fastify) => { application.addHook("onClose", (fastify) => {
fastify.io.instance.close() fastify.io.instance.close()
}) })
await application.listen({ await application.listen({
port: PORT port: PORT,
}) })
} }

View File

@ -1,10 +1,10 @@
import type { Algorithm } from 'jsonwebtoken' import type { Algorithm } from "jsonwebtoken"
import jwt from 'jsonwebtoken' import jwt from "jsonwebtoken"
import type { Socket } from 'socket.io' import type { Socket } from "socket.io"
import { UnauthorizedError } from './UnauthorizedError.js' import { UnauthorizedError } from "./UnauthorizedError.js"
declare module 'socket.io' { declare module "socket.io" {
interface Socket extends ExtendedSocket {} interface Socket extends ExtendedSocket {}
} }
@ -16,7 +16,7 @@ interface ExtendedSocket {
type SocketIOMiddleware = ( type SocketIOMiddleware = (
socket: Socket, socket: Socket,
next: (error?: UnauthorizedError) => void next: (error?: UnauthorizedError) => void,
) => void ) => void
interface CompleteDecodedToken { interface CompleteDecodedToken {
@ -28,7 +28,7 @@ interface CompleteDecodedToken {
} }
type SecretCallback = ( type SecretCallback = (
decodedToken: CompleteDecodedToken decodedToken: CompleteDecodedToken,
) => Promise<string> | string ) => Promise<string> | string
export interface AuthorizeOptions { export interface AuthorizeOptions {
@ -38,32 +38,32 @@ export interface AuthorizeOptions {
} }
export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => { export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
const { secret, algorithms = ['HS256'], onAuthentication } = options const { secret, algorithms = ["HS256"], onAuthentication } = 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 { token } = socket.handshake.auth
if (token != null) { if (token != null) {
const tokenSplitted = token.split(' ') const tokenSplitted = token.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",
}) }),
) )
} }
socket.encodedToken = encodedToken socket.encodedToken = encodedToken
let keySecret: string | null = null let keySecret: string | null = null
let decodedToken: any = null let decodedToken: any = null
if (typeof secret === 'string') { if (typeof secret === "string") {
keySecret = secret keySecret = secret
} else { } else {
const completeDecodedToken = jwt.decode(encodedToken, { complete: true }) const completeDecodedToken = jwt.decode(encodedToken, { complete: true })
@ -73,9 +73,9 @@ export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
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",
}) }),
) )
} }
socket.decodedToken = decodedToken socket.decodedToken = decodedToken

View File

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