mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
feat: add OIDC userinfo endpoint for ChatGPT domain claiming
ChatGPT enables OIDC when it finds /.well-known/openid-configuration and uses the userinfo endpoint to fetch the authenticated user's email for authorization domain claiming. - Add GET /oauth/userinfo: validates Bearer token, returns sub/email/ email_verified/preferred_username from the OAuth access token - Add userinfo_endpoint to /.well-known/openid-configuration response - Add /oauth/userinfo to open-CORS pre-middleware
This commit is contained in:
+9
-3
@@ -108,6 +108,7 @@ export function createApp(): express.Application {
|
||||
req.path.startsWith('/.well-known/') ||
|
||||
req.path === '/oauth/register' ||
|
||||
req.path === '/oauth/authorize' ||
|
||||
req.path === '/oauth/userinfo' ||
|
||||
req.path === '/mcp'
|
||||
) {
|
||||
cors({ origin: '*', credentials: false })(req, _res, next);
|
||||
@@ -424,10 +425,15 @@ export function createApp(): express.Application {
|
||||
});
|
||||
|
||||
// ChatGPT (and other OIDC-first clients) bootstrap OAuth discovery via
|
||||
// /.well-known/openid-configuration. Serve the same typed AS metadata so
|
||||
// they can find registration_endpoint, authorization_endpoint, token_endpoint.
|
||||
// /.well-known/openid-configuration. Serve the AS metadata plus the OIDC
|
||||
// userinfo_endpoint so ChatGPT can fetch the authenticated user's email
|
||||
// for authorization domain claiming.
|
||||
app.get('/.well-known/openid-configuration', (_req: Request, res: Response) => {
|
||||
res.json(getOAuthMetadata());
|
||||
const meta = getOAuthMetadata();
|
||||
res.json({
|
||||
...meta,
|
||||
userinfo_endpoint: `${meta.issuer}/oauth/userinfo`,
|
||||
});
|
||||
});
|
||||
|
||||
// SDK authorize handler: validates OAuth params, calls provider.authorize() which redirects
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
rotateOAuthClientSecret,
|
||||
listOAuthSessions,
|
||||
revokeSession,
|
||||
getUserByAccessToken,
|
||||
AuthorizeParams,
|
||||
} from '../services/oauthService';
|
||||
import { writeAudit, getClientIp, logWarn } from '../services/auditLog';
|
||||
@@ -153,6 +154,29 @@ oauthPublicRouter.post('/oauth/token', tokenLimiter, (req: Request, res: Respons
|
||||
return res.status(400).json({ error: 'unsupported_grant_type', error_description: `Unsupported grant_type: ${grant_type}` });
|
||||
});
|
||||
|
||||
// OIDC UserInfo endpoint (RFC 9068 / OpenID Connect Core §5.3)
|
||||
// ChatGPT hits this after OAuth to fetch the authenticated user's email for domain claiming.
|
||||
oauthPublicRouter.get('/oauth/userinfo', (req: Request, res: Response) => {
|
||||
if (!isAddonEnabled(ADDON_IDS.MCP)) return res.status(404).end();
|
||||
const auth = req.headers['authorization'];
|
||||
if (!auth || !auth.toLowerCase().startsWith('bearer ')) {
|
||||
res.set('WWW-Authenticate', 'Bearer realm="TREK MCP"');
|
||||
return res.status(401).json({ error: 'invalid_token' });
|
||||
}
|
||||
const token = auth.slice(7);
|
||||
const info = getUserByAccessToken(token);
|
||||
if (!info) {
|
||||
res.set('WWW-Authenticate', 'Bearer realm="TREK MCP", error="invalid_token"');
|
||||
return res.status(401).json({ error: 'invalid_token' });
|
||||
}
|
||||
return res.json({
|
||||
sub: String(info.user.id),
|
||||
email: info.user.email,
|
||||
email_verified: true,
|
||||
preferred_username: info.user.username,
|
||||
});
|
||||
});
|
||||
|
||||
// Token revocation endpoint (RFC 7009)
|
||||
oauthPublicRouter.post('/oauth/revoke', revokeLimiter, (req: Request, res: Response) => {
|
||||
if (!isAddonEnabled(ADDON_IDS.MCP)) return res.status(404).end();
|
||||
|
||||
Reference in New Issue
Block a user