Skip to content

Commit 33ad7e7

Browse files
author
subtype
committed
Merge branch 'rc-v1' into 'v1'
use express-jwt instead of manual See merge request subtype/subspace-api!5
2 parents aa810e9 + 714f137 commit 33ad7e7

File tree

4 files changed

+79
-54
lines changed

4 files changed

+79
-54
lines changed

package-lock.json

Lines changed: 43 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"license": "ISC",
2222
"dependencies": {
2323
"@modelcontextprotocol/sdk": "^1.9.0",
24+
"express-jwt": "^8.5.1",
2425
"express-rate-limit": "^7.5.0",
2526
"jose": "^6.0.10",
2627
"prettier": "3.5.3",
@@ -29,6 +30,7 @@
2930
},
3031
"devDependencies": {
3132
"@types/express": "^5.0.1",
33+
"@types/express-jwt": "^6.0.4",
3234
"@types/jsonwebtoken": "^9.0.9",
3335
"@types/node": "^22.14.1",
3436
"dotenv": "^16.5.0",

src/server.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import helmet from 'helmet'
1010
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
1111
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
1212
import { registerTools } from './v1/mcp/registerTools.js'
13-
import { validateJWT, authRequired } from './utils/auth.js'
13+
import { logIncomingAuth, authRequired, authOptional } from './utils/auth.js'
1414

1515

1616
logger.info('Initializing MCP server...')
@@ -33,7 +33,7 @@ logger.info('Registering tools with MCP server...')
3333
registerTools(mcpServer)
3434

3535
// Discovery endpoint
36-
server.get('/sse', authRequired, async (req: Request, res: Response) => {
36+
server.get('/sse', logIncomingAuth, authRequired, async (req: Request, res: Response) => {
3737

3838
const transport = new SSEServerTransport('/messages', res)
3939
transports[transport.sessionId] = transport
@@ -46,7 +46,7 @@ server.get('/sse', authRequired, async (req: Request, res: Response) => {
4646
})
4747

4848
// MCP Handler
49-
server.post('/messages', authRequired, async (req: Request, res: Response) => {
49+
server.post('/messages', logIncomingAuth, authRequired, async (req: Request, res: Response) => {
5050
const sessionId = req.query.sessionId as string
5151

5252
if (typeof sessionId != 'string') {
@@ -63,18 +63,22 @@ server.post('/messages', authRequired, async (req: Request, res: Response) => {
6363
}
6464
})
6565

66+
logger.info('Setting up middleware...')
67+
// Set up rate limiting
68+
const limiter = rateLimit({ windowMs: 60 * 1000, max: 60 })
69+
70+
server.use(helmet())
71+
server.use(limiter)
72+
6673
logger.info('Initializing routes...')
6774
// Declare regular REST API routing
6875
server.use('/', express.json(), statusRouter)
6976
server.use('/v1/trmnl', express.json(), trmnlRouter)
7077
server.use('/health', express.json(), statusRouter)
7178

72-
logger.info('Setting up middleware...')
73-
// Set up rate limiting
74-
const limiter = rateLimit({ windowMs: 60 * 1000, max: 60 })
79+
// reverse proxy
80+
server.set('trust proxy', 1)
7581

76-
server.use(limiter)
77-
server.use(helmet())
7882

7983
server.listen(PORT, () => {
8084
logger.info(`Using log level: ${process.env.LOG_LEVEL || 'info'}`)

src/utils/auth.ts

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,31 @@
1+
import { expressjwt } from 'express-jwt'
12
import { Request, Response, NextFunction } from 'express'
23
import { logger } from './logger.js'
3-
import jwt, { Secret } from 'jsonwebtoken'
44

5-
export function validateJWT(req: Request) {
6-
const secret = process.env.JWT_SECRET
7-
if (!secret) {
8-
logger.error('JWT secret is not defined in the environment!')
9-
return null
10-
}
11-
const authHeader = req.headers.authorization
5+
const JWT_SECRET = process.env.JWT_SECRET
6+
7+
export function logIncomingAuth(req: Request, res: Response, next: NextFunction) {
128
logger.debug('Incoming headers:', req.headers)
13-
if (!authHeader?.startsWith('Bearer ')) {
14-
logger.error('No auth header!')
15-
return null
16-
}
17-
// Get token from auth header since it'd look like "Bearer <token>"
18-
const token = authHeader.split(' ')[1]
19-
logger.debug('Inspecting token', token)
209

21-
try {
22-
const decodedToken = jwt.verify(token, secret as Secret)
23-
return decodedToken
24-
} catch (error) {
25-
logger.warn('JWT validation failed', error)
26-
return null
10+
const authHeader = req.headers.authorization
11+
if (!authHeader?.startsWith('Bearer ')) {
12+
logger.warn('No auth header or bad format')
13+
} else {
14+
const token = authHeader.split(' ')[1]
15+
logger.debug('Inspecting token:', token)
2716
}
28-
}
2917

30-
export async function authRequired(req: Request, res: Response, next: NextFunction) {
31-
const user = validateJWT(req)
32-
if (!user) {
33-
logger.warn('Incoming request failed JWT validation')
34-
res.status(401).send({ message: 'Unauthorized' })
35-
}
36-
// handle user logic way way way later
3718
next()
3819
}
20+
21+
export const authRequired = expressjwt({
22+
secret: JWT_SECRET!,
23+
algorithms: ['HS256'],
24+
credentialsRequired: true,
25+
})
26+
27+
export const authOptional = expressjwt({
28+
secret: JWT_SECRET!,
29+
algorithms: ['HS256'],
30+
credentialsRequired: false,
31+
})

0 commit comments

Comments
 (0)