mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +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.startsWith('/.well-known/') ||
|
||||||
req.path === '/oauth/register' ||
|
req.path === '/oauth/register' ||
|
||||||
req.path === '/oauth/authorize' ||
|
req.path === '/oauth/authorize' ||
|
||||||
|
req.path === '/oauth/userinfo' ||
|
||||||
req.path === '/mcp'
|
req.path === '/mcp'
|
||||||
) {
|
) {
|
||||||
cors({ origin: '*', credentials: false })(req, _res, next);
|
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
|
// ChatGPT (and other OIDC-first clients) bootstrap OAuth discovery via
|
||||||
// /.well-known/openid-configuration. Serve the same typed AS metadata so
|
// /.well-known/openid-configuration. Serve the AS metadata plus the OIDC
|
||||||
// they can find registration_endpoint, authorization_endpoint, token_endpoint.
|
// 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) => {
|
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
|
// SDK authorize handler: validates OAuth params, calls provider.authorize() which redirects
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import {
|
|||||||
rotateOAuthClientSecret,
|
rotateOAuthClientSecret,
|
||||||
listOAuthSessions,
|
listOAuthSessions,
|
||||||
revokeSession,
|
revokeSession,
|
||||||
|
getUserByAccessToken,
|
||||||
AuthorizeParams,
|
AuthorizeParams,
|
||||||
} from '../services/oauthService';
|
} from '../services/oauthService';
|
||||||
import { writeAudit, getClientIp, logWarn } from '../services/auditLog';
|
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}` });
|
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)
|
// Token revocation endpoint (RFC 7009)
|
||||||
oauthPublicRouter.post('/oauth/revoke', revokeLimiter, (req: Request, res: Response) => {
|
oauthPublicRouter.post('/oauth/revoke', revokeLimiter, (req: Request, res: Response) => {
|
||||||
if (!isAddonEnabled(ADDON_IDS.MCP)) return res.status(404).end();
|
if (!isAddonEnabled(ADDON_IDS.MCP)) return res.status(404).end();
|
||||||
|
|||||||
Reference in New Issue
Block a user