Files
SnowLuma/.pnpm-store/v11/files/78/c14d1a004a37e1a22b9a591ce2dda4daf878e01a0304fa672ae5e7825b47a0a9ec0aad438f72e237bdbdd6a65872117b09e72597b030d9b33775cdedffb66f
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

88 lines
4.2 KiB
Plaintext

import { InsufficientScopeError, InvalidTokenError, OAuthError, ServerError } from '../errors.js';
// The serialized value without its fragment and without one trailing slash.
function comparableResource(value) {
const text = String(value);
const hash = text.indexOf('#');
return (hash === -1 ? text : text.slice(0, hash)).replace(/\/$/, '');
}
// A reported resource matches when it serializes to the same string as the expected one, fragment and one trailing slash aside.
function sameResource(reported, expected) {
if (!reported)
return false;
return comparableResource(reported) === comparableResource(expected);
}
/**
* 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, expectedResource }) {
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 the token was issued for this server (if configured)
if (expectedResource !== undefined && !sameResource(authInfo.resource, expectedResource)) {
throw new InvalidTokenError('Token was not issued for this resource');
}
// 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