n8n
Version:
n8n Workflow Automation Tool
213 lines • 9.21 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthService = void 0;
const config_1 = require("@n8n/config");
const di_1 = require("@n8n/di");
const crypto_1 = require("crypto");
const jsonwebtoken_1 = require("jsonwebtoken");
const n8n_core_1 = require("n8n-core");
const config_2 = __importDefault(require("../config"));
const constants_1 = require("../constants");
const invalid_auth_token_repository_1 = require("../databases/repositories/invalid-auth-token.repository");
const user_repository_1 = require("../databases/repositories/user.repository");
const auth_error_1 = require("../errors/response-errors/auth.error");
const forbidden_error_1 = require("../errors/response-errors/forbidden.error");
const license_1 = require("../license");
const jwt_service_1 = require("../services/jwt.service");
const url_service_1 = require("../services/url.service");
let AuthService = class AuthService {
constructor(globalConfig, logger, license, jwtService, urlService, userRepository, invalidAuthTokenRepository) {
this.globalConfig = globalConfig;
this.logger = logger;
this.license = license;
this.jwtService = jwtService;
this.urlService = urlService;
this.userRepository = userRepository;
this.invalidAuthTokenRepository = invalidAuthTokenRepository;
this.authMiddleware = this.authMiddleware.bind(this);
const restEndpoint = globalConfig.endpoints.rest;
this.skipBrowserIdCheckEndpoints = [
`/${restEndpoint}/push`,
`/${restEndpoint}/binary-data/`,
`/${restEndpoint}/oauth1-credential/callback`,
`/${restEndpoint}/oauth2-credential/callback`,
];
}
async authMiddleware(req, res, next) {
const token = req.cookies[constants_1.AUTH_COOKIE_NAME];
if (token) {
try {
const isInvalid = await this.invalidAuthTokenRepository.existsBy({ token });
if (isInvalid)
throw new auth_error_1.AuthError('Unauthorized');
req.user = await this.resolveJwt(token, req, res);
}
catch (error) {
if (error instanceof jsonwebtoken_1.JsonWebTokenError || error instanceof auth_error_1.AuthError) {
this.clearCookie(res);
}
else {
throw error;
}
}
}
if (req.user)
next();
else
res.status(401).json({ status: 'error', message: 'Unauthorized' });
}
clearCookie(res) {
res.clearCookie(constants_1.AUTH_COOKIE_NAME);
}
async invalidateToken(req) {
const token = req.cookies[constants_1.AUTH_COOKIE_NAME];
if (!token)
return;
try {
const { exp } = this.jwtService.decode(token);
if (exp) {
await this.invalidAuthTokenRepository.insert({
token,
expiresAt: new Date(exp * 1000),
});
}
}
catch (e) {
this.logger.warn('failed to invalidate auth token', { error: e.message });
}
}
issueCookie(res, user, browserId) {
const isWithinUsersLimit = this.license.isWithinUsersLimit();
if (config_2.default.getEnv('userManagement.isInstanceOwnerSetUp') &&
!user.isOwner &&
!isWithinUsersLimit) {
throw new forbidden_error_1.ForbiddenError(constants_1.RESPONSE_ERROR_MESSAGES.USERS_QUOTA_REACHED);
}
const token = this.issueJWT(user, browserId);
const { samesite, secure } = this.globalConfig.auth.cookie;
res.cookie(constants_1.AUTH_COOKIE_NAME, token, {
maxAge: this.jwtExpiration * constants_1.Time.seconds.toMilliseconds,
httpOnly: true,
sameSite: samesite,
secure,
});
}
issueJWT(user, browserId) {
const payload = {
id: user.id,
hash: this.createJWTHash(user),
browserId: browserId && this.hash(browserId),
};
return this.jwtService.sign(payload, {
expiresIn: this.jwtExpiration,
});
}
async resolveJwt(token, req, res) {
const jwtPayload = this.jwtService.verify(token, {
algorithms: ['HS256'],
});
const user = await this.userRepository.findOne({
where: { id: jwtPayload.id },
});
if (!user ||
user.disabled ||
jwtPayload.hash !== this.createJWTHash(user)) {
throw new auth_error_1.AuthError('Unauthorized');
}
const endpoint = req.route ? `${req.baseUrl}${req.route.path}` : req.baseUrl;
if (req.method === 'GET' && this.skipBrowserIdCheckEndpoints.includes(endpoint)) {
this.logger.debug(`Skipped browserId check on ${endpoint}`);
}
else if (jwtPayload.browserId &&
(!req.browserId || jwtPayload.browserId !== this.hash(req.browserId))) {
this.logger.warn(`browserId check failed on ${endpoint}`);
throw new auth_error_1.AuthError('Unauthorized');
}
if (jwtPayload.exp * 1000 - Date.now() < this.jwtRefreshTimeout) {
this.logger.debug('JWT about to expire. Will be refreshed');
this.issueCookie(res, user, req.browserId);
}
return user;
}
generatePasswordResetToken(user, expiresIn = '20m') {
const payload = { sub: user.id, hash: this.createJWTHash(user) };
return this.jwtService.sign(payload, { expiresIn });
}
generatePasswordResetUrl(user) {
const instanceBaseUrl = this.urlService.getInstanceBaseUrl();
const url = new URL(`${instanceBaseUrl}/change-password`);
url.searchParams.append('token', this.generatePasswordResetToken(user));
url.searchParams.append('mfaEnabled', user.mfaEnabled.toString());
return url.toString();
}
async resolvePasswordResetToken(token) {
let decodedToken;
try {
decodedToken = this.jwtService.verify(token);
}
catch (e) {
if (e instanceof jsonwebtoken_1.TokenExpiredError) {
this.logger.debug('Reset password token expired', { token });
}
else {
this.logger.debug('Error verifying token', { token });
}
return;
}
const user = await this.userRepository.findOne({
where: { id: decodedToken.sub },
relations: ['authIdentities'],
});
if (!user) {
this.logger.debug('Request to resolve password token failed because no user was found for the provided user ID', { userId: decodedToken.sub, token });
return;
}
if (decodedToken.hash !== this.createJWTHash(user)) {
this.logger.debug('Password updated since this token was generated');
return;
}
return user;
}
createJWTHash({ email, password }) {
return this.hash(email + ':' + password).substring(0, 10);
}
hash(input) {
return (0, crypto_1.createHash)('sha256').update(input).digest('base64');
}
get jwtRefreshTimeout() {
const { jwtRefreshTimeoutHours, jwtSessionDurationHours } = config_2.default.get('userManagement');
if (jwtRefreshTimeoutHours === 0) {
return Math.floor(jwtSessionDurationHours * 0.25 * constants_1.Time.hours.toMilliseconds);
}
else {
return Math.floor(jwtRefreshTimeoutHours * constants_1.Time.hours.toMilliseconds);
}
}
get jwtExpiration() {
return config_2.default.get('userManagement.jwtSessionDurationHours') * constants_1.Time.hours.toSeconds;
}
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [config_1.GlobalConfig,
n8n_core_1.Logger,
license_1.License,
jwt_service_1.JwtService,
url_service_1.UrlService,
user_repository_1.UserRepository,
invalid_auth_token_repository_1.InvalidAuthTokenRepository])
], AuthService);
//# sourceMappingURL=auth.service.js.map