Commit Inicial
This commit is contained in:
Generated
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
import { RequestHandler } from 'express';
|
||||
/**
|
||||
* Middleware to handle unsupported HTTP methods with a 405 Method Not Allowed response.
|
||||
*
|
||||
* @param allowedMethods Array of allowed HTTP methods for this endpoint (e.g., ['GET', 'POST'])
|
||||
* @returns Express middleware that returns a 405 error if method not in allowed list
|
||||
*/
|
||||
export declare function allowedMethods(allowedMethods: string[]): RequestHandler;
|
||||
//# sourceMappingURL=allowedMethods.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"allowedMethods.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/allowedMethods.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,cAAc,CAUvE"}
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import { MethodNotAllowedError } from '../errors.js';
|
||||
/**
|
||||
* Middleware to handle unsupported HTTP methods with a 405 Method Not Allowed response.
|
||||
*
|
||||
* @param allowedMethods Array of allowed HTTP methods for this endpoint (e.g., ['GET', 'POST'])
|
||||
* @returns Express middleware that returns a 405 error if method not in allowed list
|
||||
*/
|
||||
export function allowedMethods(allowedMethods) {
|
||||
return (req, res, next) => {
|
||||
if (allowedMethods.includes(req.method)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
const error = new MethodNotAllowedError(`The method ${req.method} is not allowed for this endpoint`);
|
||||
res.status(405).set('Allow', allowedMethods.join(', ')).json(error.toResponseObject());
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=allowedMethods.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"allowedMethods.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/allowedMethods.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,cAAwB;IACnD,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACtB,IAAI,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,IAAI,EAAE,CAAC;YACP,OAAO;QACX,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,qBAAqB,CAAC,cAAc,GAAG,CAAC,MAAM,mCAAmC,CAAC,CAAC;QACrG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAC3F,CAAC,CAAC;AACN,CAAC"}
|
||||
Generated
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import { OAuthTokenVerifier } from '../provider.js';
|
||||
import { AuthInfo } from '../types.js';
|
||||
export type BearerAuthMiddlewareOptions = {
|
||||
/**
|
||||
* A provider used to verify tokens.
|
||||
*/
|
||||
verifier: OAuthTokenVerifier;
|
||||
/**
|
||||
* Optional scopes that the token must have.
|
||||
*/
|
||||
requiredScopes?: string[];
|
||||
/**
|
||||
* Optional resource metadata URL to include in WWW-Authenticate header.
|
||||
*/
|
||||
resourceMetadataUrl?: string;
|
||||
};
|
||||
declare module 'express-serve-static-core' {
|
||||
interface Request {
|
||||
/**
|
||||
* Information about the validated access token, if the `requireBearerAuth` middleware was used.
|
||||
*/
|
||||
auth?: AuthInfo;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Middleware that requires a valid Bearer token in the Authorization header.
|
||||
*
|
||||
* This will validate the token with the auth provider and add the resulting auth info to the request object.
|
||||
*
|
||||
* If resourceMetadataUrl is provided, it will be included in the WWW-Authenticate header
|
||||
* for 401 responses as per the OAuth 2.0 Protected Resource Metadata spec.
|
||||
*/
|
||||
export declare function requireBearerAuth({ verifier, requiredScopes, resourceMetadataUrl }: BearerAuthMiddlewareOptions): RequestHandler;
|
||||
//# sourceMappingURL=bearerAuth.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"bearerAuth.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/bearerAuth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,MAAM,2BAA2B,GAAG;IACtC;;OAEG;IACH,QAAQ,EAAE,kBAAkB,CAAC;IAE7B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAE1B;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEF,OAAO,QAAQ,2BAA2B,CAAC;IACvC,UAAU,OAAO;QACb;;WAEG;QACH,IAAI,CAAC,EAAE,QAAQ,CAAC;KACnB;CACJ;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,cAAmB,EAAE,mBAAmB,EAAE,EAAE,2BAA2B,GAAG,cAAc,CA8DrI"}
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
import { InsufficientScopeError, InvalidTokenError, OAuthError, ServerError } from '../errors.js';
|
||||
/**
|
||||
* Middleware that requires a valid Bearer token in the Authorization header.
|
||||
*
|
||||
* This will validate the token with the auth provider and add the resulting auth info to the request object.
|
||||
*
|
||||
* If resourceMetadataUrl is provided, it will be included in the WWW-Authenticate header
|
||||
* for 401 responses as per the OAuth 2.0 Protected Resource Metadata spec.
|
||||
*/
|
||||
export function requireBearerAuth({ verifier, requiredScopes = [], resourceMetadataUrl }) {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader) {
|
||||
throw new InvalidTokenError('Missing Authorization header');
|
||||
}
|
||||
const [type, token] = authHeader.split(' ');
|
||||
if (type.toLowerCase() !== 'bearer' || !token) {
|
||||
throw new InvalidTokenError("Invalid Authorization header format, expected 'Bearer TOKEN'");
|
||||
}
|
||||
const authInfo = await verifier.verifyAccessToken(token);
|
||||
// Check if token has the required scopes (if any)
|
||||
if (requiredScopes.length > 0) {
|
||||
const hasAllScopes = requiredScopes.every(scope => authInfo.scopes.includes(scope));
|
||||
if (!hasAllScopes) {
|
||||
throw new InsufficientScopeError('Insufficient scope');
|
||||
}
|
||||
}
|
||||
// Check if the token is set to expire or if it is expired
|
||||
if (typeof authInfo.expiresAt !== 'number' || isNaN(authInfo.expiresAt)) {
|
||||
throw new InvalidTokenError('Token has no expiration time');
|
||||
}
|
||||
else if (authInfo.expiresAt < Date.now() / 1000) {
|
||||
throw new InvalidTokenError('Token has expired');
|
||||
}
|
||||
req.auth = authInfo;
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
// Build WWW-Authenticate header parts
|
||||
const buildWwwAuthHeader = (errorCode, message) => {
|
||||
let header = `Bearer error="${errorCode}", error_description="${message}"`;
|
||||
if (requiredScopes.length > 0) {
|
||||
header += `, scope="${requiredScopes.join(' ')}"`;
|
||||
}
|
||||
if (resourceMetadataUrl) {
|
||||
header += `, resource_metadata="${resourceMetadataUrl}"`;
|
||||
}
|
||||
return header;
|
||||
};
|
||||
if (error instanceof InvalidTokenError) {
|
||||
res.set('WWW-Authenticate', buildWwwAuthHeader(error.errorCode, error.message));
|
||||
res.status(401).json(error.toResponseObject());
|
||||
}
|
||||
else if (error instanceof InsufficientScopeError) {
|
||||
res.set('WWW-Authenticate', buildWwwAuthHeader(error.errorCode, error.message));
|
||||
res.status(403).json(error.toResponseObject());
|
||||
}
|
||||
else if (error instanceof ServerError) {
|
||||
res.status(500).json(error.toResponseObject());
|
||||
}
|
||||
else if (error instanceof OAuthError) {
|
||||
res.status(400).json(error.toResponseObject());
|
||||
}
|
||||
else {
|
||||
const serverError = new ServerError('Internal Server Error');
|
||||
res.status(500).json(serverError.toResponseObject());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=bearerAuth.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"bearerAuth.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/bearerAuth.ts"],"names":[],"mappings":"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"}
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import { RequestHandler } from 'express';
|
||||
import { OAuthRegisteredClientsStore } from '../clients.js';
|
||||
import { OAuthClientInformationFull } from '../../../shared/auth.js';
|
||||
export type ClientAuthenticationMiddlewareOptions = {
|
||||
/**
|
||||
* A store used to read information about registered OAuth clients.
|
||||
*/
|
||||
clientsStore: OAuthRegisteredClientsStore;
|
||||
};
|
||||
declare module 'express-serve-static-core' {
|
||||
interface Request {
|
||||
/**
|
||||
* The authenticated client for this request, if the `authenticateClient` middleware was used.
|
||||
*/
|
||||
client?: OAuthClientInformationFull;
|
||||
}
|
||||
}
|
||||
export declare function authenticateClient({ clientsStore }: ClientAuthenticationMiddlewareOptions): RequestHandler;
|
||||
//# sourceMappingURL=clientAuth.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"clientAuth.d.ts","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/clientAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAGrE,MAAM,MAAM,qCAAqC,GAAG;IAChD;;OAEG;IACH,YAAY,EAAE,2BAA2B,CAAC;CAC7C,CAAC;AAOF,OAAO,QAAQ,2BAA2B,CAAC;IACvC,UAAU,OAAO;QACb;;WAEG;QACH,MAAM,CAAC,EAAE,0BAA0B,CAAC;KACvC;CACJ;AAED,wBAAgB,kBAAkB,CAAC,EAAE,YAAY,EAAE,EAAE,qCAAqC,GAAG,cAAc,CAoC1G"}
|
||||
Generated
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
import * as z from 'zod/v4';
|
||||
import { InvalidRequestError, InvalidClientError, ServerError, OAuthError } from '../errors.js';
|
||||
const ClientAuthenticatedRequestSchema = z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string().optional()
|
||||
});
|
||||
export function authenticateClient({ clientsStore }) {
|
||||
return async (req, res, next) => {
|
||||
try {
|
||||
const result = ClientAuthenticatedRequestSchema.safeParse(req.body);
|
||||
if (!result.success) {
|
||||
throw new InvalidRequestError(String(result.error));
|
||||
}
|
||||
const { client_id, client_secret } = result.data;
|
||||
const client = await clientsStore.getClient(client_id);
|
||||
if (!client) {
|
||||
throw new InvalidClientError('Invalid client_id');
|
||||
}
|
||||
if (client.client_secret) {
|
||||
if (!client_secret) {
|
||||
throw new InvalidClientError('Client secret is required');
|
||||
}
|
||||
if (client.client_secret !== client_secret) {
|
||||
throw new InvalidClientError('Invalid client_secret');
|
||||
}
|
||||
if (client.client_secret_expires_at && client.client_secret_expires_at < Math.floor(Date.now() / 1000)) {
|
||||
throw new InvalidClientError('Client secret has expired');
|
||||
}
|
||||
}
|
||||
req.client = client;
|
||||
next();
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof OAuthError) {
|
||||
const status = error instanceof ServerError ? 500 : 400;
|
||||
res.status(status).json(error.toResponseObject());
|
||||
}
|
||||
else {
|
||||
const serverError = new ServerError('Internal Server Error');
|
||||
res.status(500).json(serverError.toResponseObject());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=clientAuth.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"clientAuth.js","sourceRoot":"","sources":["../../../../../src/server/auth/middleware/clientAuth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAI5B,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAShG,MAAM,gCAAgC,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC;AAWH,MAAM,UAAU,kBAAkB,CAAC,EAAE,YAAY,EAAyC;IACtF,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC5B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,gCAAgC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACpE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACxD,CAAC;YACD,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC;YACjD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;YACvD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACV,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;YACtD,CAAC;YACD,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;gBACvB,IAAI,CAAC,aAAa,EAAE,CAAC;oBACjB,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,CAAC,CAAC;gBAC9D,CAAC;gBACD,IAAI,MAAM,CAAC,aAAa,KAAK,aAAa,EAAE,CAAC;oBACzC,MAAM,IAAI,kBAAkB,CAAC,uBAAuB,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,MAAM,CAAC,wBAAwB,IAAI,MAAM,CAAC,wBAAwB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;oBACrG,MAAM,IAAI,kBAAkB,CAAC,2BAA2B,CAAC,CAAC;gBAC9D,CAAC;YACL,CAAC;YAED,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC;QACX,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;gBAC9B,MAAM,MAAM,GAAG,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBACxD,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACtD,CAAC;iBAAM,CAAC;gBACJ,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,uBAAuB,CAAC,CAAC;gBAC7D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB,EAAE,CAAC,CAAC;YACzD,CAAC;QACL,CAAC;IACL,CAAC,CAAC;AACN,CAAC"}
|
||||
Reference in New Issue
Block a user