Files
SnowLuma/.pnpm-store/v11/files/de/5debf9daed7c32f6b0700aedf4ab3ac198d42d58b687f17241ae12a2a55bd106ee0685f6d0db8e46e5dbe2b9c93e70f0e1fd96dbc70237ef52e38706ec5904
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

91 lines
4.4 KiB
Plaintext

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireBearerAuth = requireBearerAuth;
const errors_js_1 = require("../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.
*/
function requireBearerAuth({ verifier, requiredScopes = [], resourceMetadataUrl, expectedResource }) {
return async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
throw new errors_js_1.InvalidTokenError('Missing Authorization header');
}
const [type, token] = authHeader.split(' ');
if (type.toLowerCase() !== 'bearer' || !token) {
throw new errors_js_1.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 errors_js_1.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 errors_js_1.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 errors_js_1.InvalidTokenError('Token has no expiration time');
}
else if (authInfo.expiresAt < Date.now() / 1000) {
throw new errors_js_1.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 errors_js_1.InvalidTokenError) {
res.set('WWW-Authenticate', buildWwwAuthHeader(error.errorCode, error.message));
res.status(401).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.InsufficientScopeError) {
res.set('WWW-Authenticate', buildWwwAuthHeader(error.errorCode, error.message));
res.status(403).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.ServerError) {
res.status(500).json(error.toResponseObject());
}
else if (error instanceof errors_js_1.OAuthError) {
res.status(400).json(error.toResponseObject());
}
else {
const serverError = new errors_js_1.ServerError('Internal Server Error');
res.status(500).json(serverError.toResponseObject());
}
}
};
}
//# sourceMappingURL=bearerAuth.js.map