WIP /token endpoint
This commit is contained in:
parent
62cd9520da
commit
5738f0cef1
2 changed files with 127 additions and 0 deletions
122
src/server/auth/handlers/token.ts
Normal file
122
src/server/auth/handlers/token.ts
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import { z } from "zod";
|
||||
import express, { RequestHandler, Response } from "express";
|
||||
import { OAuthServerProvider } from "../provider.js";
|
||||
import cors from "cors";
|
||||
import { AuthorizationHandlerOptions } from "./authorize.js";
|
||||
import { OAuthClientInformationFull } from "../../../shared/auth.js";
|
||||
|
||||
export type TokenHandlerOptions = {
|
||||
provider: OAuthServerProvider;
|
||||
};
|
||||
|
||||
const TokenRequestSchema = z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string().optional(),
|
||||
grant_type: z.string(),
|
||||
});
|
||||
|
||||
const AuthorizationCodeGrantSchema = z.object({
|
||||
code: z.string(),
|
||||
code_verifier: z.string(),
|
||||
});
|
||||
|
||||
const RefreshTokenGrantSchema = z.object({
|
||||
refresh_token: z.string(),
|
||||
scope: z.string().optional(),
|
||||
});
|
||||
|
||||
export function tokenHandler({ provider }: TokenHandlerOptions): RequestHandler {
|
||||
// Nested router so we can configure middleware and restrict HTTP method
|
||||
const router = express.Router();
|
||||
router.use(express.urlencoded({ extended: false }));
|
||||
|
||||
// Configure CORS to allow any origin, to make accessible to web-based MCP clients
|
||||
router.use(cors());
|
||||
|
||||
async function authorizationCodeGrant(client: OAuthClientInformationFull, grant: z.infer<typeof AuthorizationCodeGrantSchema>, res: Response) {
|
||||
const codeChallenge = await provider.challengeForAuthorizationCode(grant.code);
|
||||
// TODO
|
||||
}
|
||||
|
||||
async function refreshTokenGrant(client: OAuthClientInformationFull, grant: z.infer<typeof RefreshTokenGrantSchema>, res: Response) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// Actual request handler
|
||||
router.post("/", async (req, res) => {
|
||||
let client_id, client_secret, grant_type;
|
||||
try {
|
||||
({ client_id, client_secret, grant_type } = TokenRequestSchema.parse(req.body));
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
error: "invalid_request",
|
||||
error_description: String(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await provider.clientsStore.getClient(client_id);
|
||||
if (!client) {
|
||||
// TODO: Return 401 with WWW-Authenticate if Authorization header was used
|
||||
res.status(400).json({
|
||||
error: "invalid_client",
|
||||
error_description: "Invalid client_id",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (client.client_secret !== client_secret) {
|
||||
res.status(400).json({
|
||||
error: "invalid_client",
|
||||
error_description: "Invalid client_secret",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
switch (grant_type) {
|
||||
case "authorization_code": {
|
||||
let grant;
|
||||
try {
|
||||
grant = AuthorizationCodeGrantSchema.parse(req.body);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
error: "invalid_request",
|
||||
error_description: String(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await authorizationCodeGrant(client, grant, res);
|
||||
break;
|
||||
}
|
||||
|
||||
case "refresh_token": {
|
||||
let grant;
|
||||
try {
|
||||
grant = RefreshTokenGrantSchema.parse(req.body);
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
error: "invalid_request",
|
||||
error_description: String(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await refreshTokenGrant(client, grant, res);
|
||||
break;
|
||||
}
|
||||
|
||||
// Not supported right now
|
||||
//case "client_credentials":
|
||||
|
||||
default:
|
||||
res.status(400).json({
|
||||
error: "unsupported_grant_type",
|
||||
error_description: "The grant type is not supported by this authorization server.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
|
@ -29,4 +29,9 @@ export interface OAuthServerProvider {
|
|||
* - In the error case, the redirect MUST include the `error` query parameter, and MAY include an optional `error_description` query parameter.
|
||||
*/
|
||||
authorize(params: AuthorizationParams, res: Response): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns the `codeChallenge` that was used when the indicated authorization began.
|
||||
*/
|
||||
challengeForAuthorizationCode(authorizationCode: string): Promise<string>;
|
||||
}
|
||||
Loading…
Reference in a new issue