authelia/server/src/lib/authentication/backends/file/FileUsersDatabase.ts

182 lines
5.2 KiB
TypeScript
Raw Normal View History

import Bluebird = require("bluebird");
import Fs = require("fs");
import Yaml = require("yamljs");
import { FileUsersDatabaseConfiguration }
from "../../../configuration/schema/FileUsersDatabaseConfiguration";
import { GroupsAndEmails } from "../GroupsAndEmails";
import { IUsersDatabase } from "../IUsersDatabase";
import { HashGenerator } from "../../../utils/HashGenerator";
import { ReadWriteQueue } from "./ReadWriteQueue";
const loadAsync = Bluebird.promisify(Yaml.load);
export class FileUsersDatabase implements IUsersDatabase {
private configuration: FileUsersDatabaseConfiguration;
private queue: ReadWriteQueue;
constructor(configuration: FileUsersDatabaseConfiguration) {
this.configuration = configuration;
this.queue = new ReadWriteQueue(this.configuration.path);
}
/**
* Read database from file.
* It enqueues the read task so that it is scheduled
* between other reads and writes.
*/
private readDatabase(): Bluebird<any> {
return new Bluebird<string>((resolve, reject) => {
this.queue.read((err: Error, data: string) => {
if (err) {
reject(err);
return;
}
resolve(data);
this.queue.next();
});
})
2019-01-08 17:15:20 +07:00
.then((content) => {
const database = Yaml.parse(content);
if (!database) {
return Bluebird.reject(new Error("Unable to parse YAML file."));
}
return Bluebird.resolve(database);
});
}
/**
* Checks the user exists in the database.
*/
private checkUserExists(
database: any,
username: string)
: Bluebird<void> {
if (!(username in database.users)) {
return Bluebird.reject(
new Error(`User ${username} does not exist in database.`));
}
return Bluebird.resolve();
}
/**
* Check the password of a given user.
*/
private checkPassword(
database: any,
username: string,
password: string)
: Bluebird<void> {
const storedHash: string = database.users[username].password;
2019-01-08 17:15:20 +07:00
const matches = storedHash.match(/rounds=([0-9]+)\$([a-zA-z0-9.]+)\$/);
if (!(matches && matches.length == 3)) {
return Bluebird.reject(new Error("Unable to detect the hash salt and rounds. " +
"Make sure the password is hashed with SSHA512."));
}
const rounds: number = parseInt(matches[1]);
const salt = matches[2];
return HashGenerator.ssha512(password, rounds, salt)
.then((hash: string) => {
if (hash !== storedHash) {
return Bluebird.reject(new Error("Wrong username/password."));
}
return Bluebird.resolve();
});
}
/**
* Retrieve email addresses of a given user.
*/
private retrieveEmails(
database: any,
username: string)
: Bluebird<string[]> {
if (!("email" in database.users[username])) {
return Bluebird.reject(
new Error(`User ${username} has no email address.`));
}
return Bluebird.resolve(
[database.users[username].email]);
}
private retrieveGroups(
database: any,
username: string)
: Bluebird<string[]> {
if (!("groups" in database.users[username])) {
return Bluebird.resolve([]);
}
return Bluebird.resolve(
database.users[username].groups);
}
private replacePassword(
database: any,
username: string,
newPassword: string)
: Bluebird<void> {
const that = this;
return HashGenerator.ssha512(newPassword)
.then((hash) => {
database.users[username].password = hash;
const str = Yaml.stringify(database, 4, 2);
return Bluebird.resolve(str);
})
.then((content: string) => {
return new Bluebird((resolve, reject) => {
that.queue.write(content, (err) => {
if (err) {
return reject(err);
}
resolve();
that.queue.next();
});
});
});
}
checkUserPassword(
username: string,
password: string)
: Bluebird<GroupsAndEmails> {
return this.readDatabase()
.then((database) => {
return this.checkUserExists(database, username)
.then(() => this.checkPassword(database, username, password))
.then(() => {
return Bluebird.join(
this.retrieveEmails(database, username),
this.retrieveGroups(database, username)
).spread((emails: string[], groups: string[]) => {
return { emails: emails, groups: groups };
});
});
2019-01-08 17:15:20 +07:00
});
}
getEmails(username: string): Bluebird<string[]> {
return this.readDatabase()
.then((database) => {
return this.checkUserExists(database, username)
.then(() => this.retrieveEmails(database, username));
});
}
getGroups(username: string): Bluebird<string[]> {
return this.readDatabase()
.then((database) => {
return this.checkUserExists(database, username)
.then(() => this.retrieveGroups(database, username));
});
}
updatePassword(username: string, newPassword: string): Bluebird<void> {
return this.readDatabase()
.then((database) => {
return this.checkUserExists(database, username)
.then(() => this.replacePassword(database, username, newPassword));
});
}
}