Support Codex OAuth login and add CLI setup docs
This commit is contained in:
50
src/index.ts
50
src/index.ts
@@ -165,6 +165,28 @@ function extractBearerToken(req: express.Request): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractBasicClientCredentials(req: express.Request): { clientId?: string; clientSecret?: string } {
|
||||
const authHeader = req.headers.authorization as string | undefined;
|
||||
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf8');
|
||||
const separatorIndex = decoded.indexOf(':');
|
||||
if (separatorIndex === -1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
clientId: decoded.slice(0, separatorIndex),
|
||||
clientSecret: decoded.slice(separatorIndex + 1),
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function requireAuth(req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
try {
|
||||
// No API key configured = open access
|
||||
@@ -236,6 +258,8 @@ app.get('/oauth/authorize', async (req, res) => {
|
||||
const redirectUri = req.query.redirect_uri as string | undefined;
|
||||
const state = req.query.state as string | undefined;
|
||||
const scope = req.query.scope as string | undefined;
|
||||
const codeChallenge = req.query.code_challenge as string | undefined;
|
||||
const codeChallengeMethod = req.query.code_challenge_method as string | undefined;
|
||||
const responseType = req.query.response_type as string | undefined;
|
||||
|
||||
if (!clientId || !redirectUri) {
|
||||
@@ -254,7 +278,14 @@ app.get('/oauth/authorize', async (req, res) => {
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send(getAuthorizeHtml({ client_id: clientId, redirect_uri: redirectUri, state, scope }));
|
||||
res.send(getAuthorizeHtml({
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
state,
|
||||
scope,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: codeChallengeMethod,
|
||||
}));
|
||||
});
|
||||
|
||||
app.post('/oauth/authorize', async (req, res) => {
|
||||
@@ -262,6 +293,8 @@ app.post('/oauth/authorize', async (req, res) => {
|
||||
const redirectUri = req.body.redirect_uri as string | undefined;
|
||||
const state = req.body.state as string | undefined;
|
||||
const scope = req.body.scope as string | undefined;
|
||||
const codeChallenge = req.body.code_challenge as string | undefined;
|
||||
const codeChallengeMethod = req.body.code_challenge_method as string | undefined;
|
||||
const action = req.body.action as string | undefined;
|
||||
|
||||
if (!clientId || !redirectUri) {
|
||||
@@ -284,7 +317,7 @@ app.post('/oauth/authorize', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const code = await createAuthCode(clientId, redirectUri, scope);
|
||||
const code = await createAuthCode(clientId, redirectUri, scope, codeChallenge, codeChallengeMethod);
|
||||
const url = new URL(redirectUri);
|
||||
url.searchParams.set('code', code.code);
|
||||
if (state) url.searchParams.set('state', state);
|
||||
@@ -299,17 +332,19 @@ app.post('/oauth/token', async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const clientId = req.body.client_id as string | undefined;
|
||||
const clientSecret = req.body.client_secret as string | undefined;
|
||||
const basicAuth = extractBasicClientCredentials(req);
|
||||
const clientId = (req.body.client_id as string | undefined) || basicAuth.clientId;
|
||||
const clientSecret = (req.body.client_secret as string | undefined) || basicAuth.clientSecret;
|
||||
const code = req.body.code as string | undefined;
|
||||
const redirectUri = req.body.redirect_uri as string | undefined;
|
||||
const codeVerifier = req.body.code_verifier as string | undefined;
|
||||
|
||||
if (!clientId || !clientSecret || !code || !redirectUri) {
|
||||
if (!clientId || !code || !redirectUri || (!clientSecret && !codeVerifier)) {
|
||||
res.status(400).json({ error: 'invalid_request' });
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await exchangeCodeForToken(clientId, clientSecret, code, redirectUri);
|
||||
const token = await exchangeCodeForToken(clientId, clientSecret, code, redirectUri, codeVerifier);
|
||||
if (!token) {
|
||||
res.status(400).json({ error: 'invalid_grant' });
|
||||
return;
|
||||
@@ -973,7 +1008,8 @@ const oauthDiscovery = {
|
||||
registration_endpoint: `${SERVER_URL}/oauth/register`,
|
||||
response_types_supported: ['code'],
|
||||
grant_types_supported: ['authorization_code'],
|
||||
token_endpoint_auth_methods_supported: ['client_secret_post'],
|
||||
token_endpoint_auth_methods_supported: ['client_secret_post', 'client_secret_basic', 'none'],
|
||||
code_challenge_methods_supported: ['S256', 'plain'],
|
||||
};
|
||||
|
||||
const protectedResourceMetadata = {
|
||||
|
||||
Reference in New Issue
Block a user