Skip to content

Commit aa810e9

Browse files
author
subtype
committed
Merge branch 'rc-v1' into 'v1'
Rc v1 See merge request subtype/subspace-api!4
2 parents 56eed90 + 19490c0 commit aa810e9

File tree

7 files changed

+56
-26
lines changed

7 files changed

+56
-26
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,7 @@ npm run build && npm run start
2727
|---------|--------|
2828
| PORT | Defaults to 9595. The port for the API and MCP server to listen on. |
2929
| LOG_LEVEL | Defaults to 'info'. Set the logging level |
30-
| ACTIVE_VERSION | Defaults to 'v1', currently not implemented fully. |
30+
| ACTIVE_VERSION | Defaults to 'v1', currently not implemented fully. |
31+
| WMATA_PRIMARY_KEY | The API key to use for obtaining WMATA status. |
32+
| JWT_SECRET | This is the secret key that is used for encrypting and decrypting JWT tokens. |
33+
| TZ | (Optional) Lets the container/logger format log messages with the machine's local time zone. |

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"dependencies": {
2323
"@modelcontextprotocol/sdk": "^1.9.0",
2424
"express-rate-limit": "^7.5.0",
25+
"jose": "^6.0.10",
2526
"prettier": "3.5.3",
2627
"yahoo-finance2": "^2.13.3",
2728
"zod": "^3.24.2"

src/server.ts

Lines changed: 4 additions & 14 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 } from './utils/auth.js'
13+
import { validateJWT, authRequired } from './utils/auth.js'
1414

1515

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

3535
// Discovery endpoint
36-
server.get('/sse', async (req: Request, res: Response) => {
37-
if (!validateJWT(req)) {
38-
logger.warn('Incoming request has invalid or missing authorization', req.headers.authorization)
39-
res.status(401).send({ message: 'Unauthorized' })
40-
return
41-
}
36+
server.get('/sse', authRequired, async (req: Request, res: Response) => {
4237

4338
const transport = new SSEServerTransport('/messages', res)
4439
transports[transport.sessionId] = transport
4540
logger.info('New MCP session created:', transport.sessionId)
4641
res.on('close', () => {
47-
logger.info('Closing session')
42+
logger.info('Closing session', transports[transport.sessionId])
4843
delete transports[transport.sessionId]
4944
})
5045
await mcpServer.connect(transport)
5146
})
5247

5348
// MCP Handler
54-
server.post('/messages', async (req: Request, res: Response) => {
49+
server.post('/messages', authRequired, async (req: Request, res: Response) => {
5550
const sessionId = req.query.sessionId as string
56-
if (!validateJWT(req)) {
57-
logger.warn('Incoming request failed JWT validation')
58-
res.status(401).send({ message: 'Unauthorized' })
59-
return
60-
}
6151

6252
if (typeof sessionId != 'string') {
6353
logger.error('Bad sessionId', sessionId)

src/utils/auth.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
1-
import { Request } from 'express'
1+
import { Request, Response, NextFunction } from 'express'
22
import { logger } from './logger.js'
33
import jwt, { Secret } from 'jsonwebtoken'
44

5-
export function validateJWT(req: Request): boolean {
5+
export function validateJWT(req: Request) {
66
const secret = process.env.JWT_SECRET
77
if (!secret) {
88
logger.error('JWT secret is not defined in the environment!')
9-
return false
9+
return null
1010
}
1111
const authHeader = req.headers.authorization
1212
logger.debug('Incoming headers:', req.headers)
1313
if (!authHeader?.startsWith('Bearer ')) {
1414
logger.error('No auth header!')
15-
return false
15+
return null
1616
}
1717
// Get token from auth header since it'd look like "Bearer <token>"
1818
const token = authHeader.split(' ')[1]
1919
logger.debug('Inspecting token', token)
2020

2121
try {
22-
jwt.verify(token, secret as Secret)
23-
return true
22+
const decodedToken = jwt.verify(token, secret as Secret)
23+
return decodedToken
2424
} catch (error) {
2525
logger.warn('JWT validation failed', error)
26-
return false
26+
return null
2727
}
2828
}
29+
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
37+
next()
38+
}

src/utils/env.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
import dotenv from 'dotenv'
2-
import path from 'path'
3-
const currentDir = path.dirname(new URL(import.meta.url).pathname)
4-
const envPath = path.resolve(currentDir, '../../.env')
5-
dotenv.config({ path: envPath })
2+
dotenv.config({ path: '.env' })

src/utils/oauth.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { jwtVerify, createRemoteJWKSet } from 'jose'
2+
3+
const auth_server_url = process.env.AUTH_SERVER_URL
4+
5+
const JWKS = createRemoteJWKSet(new URL(auth_server_url!))
6+
7+
export async function verifyToken(token: string) {
8+
try {
9+
const { payload } = await jwtVerify(token, JWKS, {
10+
issuer: "https://auth.subtype.space/realms/subspace",
11+
audience: "account" // or your expected audience if customized
12+
})
13+
14+
return payload // this will include `sub`, `azp`, `scope`, `client_id`, etc.
15+
} catch (err) {
16+
console.error("Token verification failed:", err)
17+
return null
18+
}
19+
}

0 commit comments

Comments
 (0)