"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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs-extra");
const crypto = require("crypto");
const jwt = require("jsonwebtoken");
const jwt_1 = require("@nestjs/jwt");
const common_1 = require("@nestjs/common");
const config_service_1 = require("../config/config.service");
const logger_service_1 = require("../logger/logger.service");
const websockets_1 = require("@nestjs/websockets");
let AuthService = class AuthService {
    constructor(jwtService, configService, logger) {
        this.jwtService = jwtService;
        this.configService = configService;
        this.logger = logger;
        this.setupAuthFile();
    }
    authenticate(username, password) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const user = yield this.doLogin(username, password);
                if (user) {
                    return {
                        username: user.username,
                        name: user.name,
                        admin: user.admin,
                        instanceId: this.configService.instanceId,
                    };
                }
            }
            catch (e) {
                this.logger.warn(`Failed login attempt`);
                this.logger.warn(`If you've forgotten your password you can reset to the default ` +
                    `of admin/admin by deleting the "auth.json" file (${this.configService.authPath}) and then restarting Homebridge.`);
                throw new common_1.ForbiddenException();
            }
        });
    }
    signIn(username, password) {
        return __awaiter(this, void 0, void 0, function* () {
            const user = yield this.authenticate(username, password);
            const token = yield this.jwtService.sign(user);
            return {
                access_token: token,
                token_type: 'Bearer',
                expires_in: this.configService.ui.sessionTimeout,
            };
        });
    }
    generateNoAuthToken() {
        return __awaiter(this, void 0, void 0, function* () {
            if (this.configService.ui.auth !== 'none') {
                throw new common_1.UnauthorizedException();
            }
            const users = yield this.getUsers();
            const user = users.find(x => x.admin === true);
            const token = yield this.jwtService.sign({
                username: user.username,
                name: user.name,
                admin: user.admin,
                instanceId: this.configService.instanceId,
            });
            return {
                access_token: token,
                token_type: 'Bearer',
                expires_in: this.configService.ui.sessionTimeout,
            };
        });
    }
    validateUser(payload) {
        return __awaiter(this, void 0, void 0, function* () {
            return payload;
        });
    }
    verifyWsConnection(client) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return jwt.verify(client.handshake.query.token, this.configService.secrets.secretKey);
            }
            catch (e) {
                client.disconnect();
                throw new websockets_1.WsException('Unauthorized');
            }
        });
    }
    getUsers() {
        return __awaiter(this, void 0, void 0, function* () {
            const users = yield fs.readJson(this.configService.authPath);
            return users;
        });
    }
    findById(id) {
        return __awaiter(this, void 0, void 0, function* () {
            const users = yield this.getUsers();
            const user = users.find(x => x.id === id);
            return user;
        });
    }
    findByUsername(username) {
        return __awaiter(this, void 0, void 0, function* () {
            const users = yield this.getUsers();
            const user = users.find(x => x.username === username);
            return user;
        });
    }
    saveUserFile(users) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield fs.writeJson(this.configService.authPath, users, { spaces: 4 });
        });
    }
    hashPassword(password, salt) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                crypto.pbkdf2(password, salt, 1000, 64, 'sha512', (err, derivedKey) => {
                    if (err)
                        return reject(err);
                    return resolve(derivedKey.toString('hex'));
                });
            });
        });
    }
    genSalt() {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((resolve, reject) => {
                crypto.randomBytes(32, (err, buf) => {
                    if (err)
                        return reject(err);
                    return resolve(buf.toString('hex'));
                });
            });
        });
    }
    doLogin(username, password) {
        return __awaiter(this, void 0, void 0, function* () {
            const user = yield this.findByUsername(username);
            if (!user) {
                throw new common_1.ForbiddenException();
            }
            const hashedPassword = yield this.hashPassword(password, user.salt);
            if (hashedPassword === user.hashedPassword) {
                return user;
            }
            else {
                throw new common_1.ForbiddenException();
            }
        });
    }
    addUser(user) {
        return __awaiter(this, void 0, void 0, function* () {
            const authfile = yield this.getUsers();
            const salt = yield this.genSalt();
            const newUser = {
                id: authfile.length ? Math.max.apply(Math, authfile.map(x => x.id)) + 1 : 1,
                username: user.username,
                name: user.name,
                hashedPassword: yield this.hashPassword(user.password, salt),
                salt,
                admin: user.admin,
            };
            authfile.push(newUser);
            yield this.saveUserFile(authfile);
            this.logger.warn(`Added new user: ${user.username}`);
        });
    }
    deleteUser(id) {
        return __awaiter(this, void 0, void 0, function* () {
            const authfile = yield this.getUsers();
            const index = authfile.findIndex(x => x.id === parseInt(id, 10));
            if (index < 0) {
                throw new common_1.BadRequestException('User Not Found');
            }
            if (authfile[index].admin && authfile.filter(x => x.admin === true).length < 2) {
                throw new common_1.BadRequestException('Cannot delete only admin user');
            }
            authfile.splice(index, 1);
            yield this.saveUserFile(authfile);
            this.logger.warn(`Deleted user with ID ${id}`);
        });
    }
    updateUser(id, update) {
        return __awaiter(this, void 0, void 0, function* () {
            const authfile = yield this.getUsers();
            const user = authfile.find(x => x.id === parseInt(id, 10));
            if (!user) {
                throw new common_1.BadRequestException('User Not Found');
            }
            user.name = update.name || user.name;
            user.admin = (update.admin === undefined) ? user.admin : update.admin;
            if (update.password) {
                const salt = yield this.genSalt();
                user.hashedPassword = yield this.hashPassword(update.password, salt);
                user.salt = salt;
            }
            this.saveUserFile(authfile);
            this.logger.log(`Updated user: ${user.username}`);
        });
    }
    setupDefaultUser() {
        return __awaiter(this, void 0, void 0, function* () {
            return this.addUser({
                username: 'admin',
                password: 'admin',
                name: 'Administrator',
                admin: true,
            });
        });
    }
    setupAuthFile() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(yield fs.pathExists(this.configService.authPath))) {
                yield fs.writeJson(this.configService.authPath, []);
            }
            const authfile = yield this.getUsers();
            if (!authfile.find(x => x.admin === true || x.username === 'admin')) {
                yield this.setupDefaultUser();
            }
        });
    }
};
AuthService = __decorate([
    common_1.Injectable(),
    __metadata("design:paramtypes", [jwt_1.JwtService,
        config_service_1.ConfigService,
        logger_service_1.Logger])
], AuthService);
exports.AuthService = AuthService;
//# sourceMappingURL=auth.service.js.map