2020-12-30 14:50:56 +01:00
|
|
|
import jwt, { Algorithm } from 'jsonwebtoken'
|
2020-12-29 04:05:39 +01:00
|
|
|
import { Socket } from 'socket.io'
|
|
|
|
|
|
|
|
import { UnauthorizedError } from './UnauthorizedError'
|
|
|
|
|
2021-01-04 14:35:59 +01:00
|
|
|
declare module 'socket.io' {
|
|
|
|
interface Socket extends ExtendedSocket {}
|
|
|
|
}
|
|
|
|
|
2020-12-29 04:05:39 +01:00
|
|
|
interface ExtendedError extends Error {
|
|
|
|
data?: any
|
|
|
|
}
|
|
|
|
|
2021-01-04 14:35:59 +01:00
|
|
|
interface ExtendedSocket {
|
|
|
|
encodedToken?: string
|
|
|
|
decodedToken?: any
|
2021-03-08 13:45:39 +01:00
|
|
|
user?: any
|
2021-01-04 14:35:59 +01:00
|
|
|
}
|
|
|
|
|
2020-12-29 04:05:39 +01:00
|
|
|
type SocketIOMiddleware = (
|
|
|
|
socket: Socket,
|
|
|
|
next: (err?: ExtendedError) => void
|
|
|
|
) => void
|
|
|
|
|
2021-01-28 18:47:38 +01:00
|
|
|
interface CompleteDecodedToken {
|
|
|
|
header: {
|
|
|
|
alg: Algorithm
|
|
|
|
[key: string]: any
|
|
|
|
}
|
|
|
|
payload: any
|
|
|
|
}
|
|
|
|
|
|
|
|
type SecretCallback = (decodedToken: CompleteDecodedToken) => Promise<string>
|
2021-01-07 14:30:37 +01:00
|
|
|
|
|
|
|
export interface AuthorizeOptions {
|
|
|
|
secret: string | SecretCallback
|
2020-12-30 14:50:56 +01:00
|
|
|
algorithms?: Algorithm[]
|
2021-03-08 13:45:39 +01:00
|
|
|
onAuthentication?: (decodedToken: any) => Promise<any> | any
|
2020-12-29 04:05:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
export const authorize = (options: AuthorizeOptions): SocketIOMiddleware => {
|
2021-03-08 13:45:39 +01:00
|
|
|
const { secret, algorithms = ['HS256'], onAuthentication } = options
|
2021-01-07 14:30:37 +01:00
|
|
|
return async (socket, next) => {
|
|
|
|
let encodedToken: string | null = null
|
2021-02-22 13:00:53 +01:00
|
|
|
const { token } = socket.handshake.auth
|
|
|
|
if (token != null) {
|
|
|
|
const tokenSplitted = token.split(' ')
|
2020-12-29 04:05:39 +01:00
|
|
|
if (tokenSplitted.length !== 2 || tokenSplitted[0] !== 'Bearer') {
|
|
|
|
return next(
|
|
|
|
new UnauthorizedError('credentials_bad_format', {
|
|
|
|
message: 'Format is Authorization: Bearer [token]'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2021-01-07 14:30:37 +01:00
|
|
|
encodedToken = tokenSplitted[1]
|
2020-12-29 04:05:39 +01:00
|
|
|
}
|
2021-01-07 14:30:37 +01:00
|
|
|
if (encodedToken == null) {
|
2020-12-29 04:05:39 +01:00
|
|
|
return next(
|
|
|
|
new UnauthorizedError('credentials_required', {
|
|
|
|
message: 'no token provided'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2021-01-07 14:30:37 +01:00
|
|
|
socket.encodedToken = encodedToken
|
|
|
|
let keySecret: string | null = null
|
|
|
|
let decodedToken: any
|
|
|
|
if (typeof secret === 'string') {
|
|
|
|
keySecret = secret
|
|
|
|
} else {
|
2021-01-28 18:47:38 +01:00
|
|
|
const completeDecodedToken = jwt.decode(encodedToken, { complete: true })
|
|
|
|
keySecret = await secret(completeDecodedToken as CompleteDecodedToken)
|
2021-01-07 14:30:37 +01:00
|
|
|
}
|
2020-12-29 04:05:39 +01:00
|
|
|
try {
|
2021-01-07 14:30:37 +01:00
|
|
|
decodedToken = jwt.verify(encodedToken, keySecret, { algorithms })
|
2020-12-29 04:05:39 +01:00
|
|
|
} catch {
|
|
|
|
return next(
|
|
|
|
new UnauthorizedError('invalid_token', {
|
|
|
|
message: 'Unauthorized: Token is missing or invalid Bearer'
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2021-01-07 14:30:37 +01:00
|
|
|
socket.decodedToken = decodedToken
|
2021-03-08 13:45:39 +01:00
|
|
|
if (onAuthentication != null) {
|
|
|
|
try {
|
|
|
|
socket.user = await onAuthentication(decodedToken)
|
|
|
|
} catch (err) {
|
|
|
|
return next(err)
|
|
|
|
}
|
|
|
|
}
|
2020-12-29 04:05:39 +01:00
|
|
|
return next()
|
|
|
|
}
|
|
|
|
}
|