Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@accounts/password

Package Overview
Dependencies
Maintainers
5
Versions
139
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@accounts/password - npm Package Compare versions

Comparing version 0.1.0-alpha.b5d6f4ab to 0.1.0-alpha.cdeba60e

__tests__/index.ts

146

__tests__/accounts-password.ts

@@ -32,8 +32,8 @@ import { set } from 'lodash';

it('throws when user not found', async () => {
const user = 'user';
it('return user', async () => {
const user = {
services: {},
};
const tmpAccountsPassword = new AccountsPassword({});
tmpAccountsPassword.passwordAuthenticator = jest.fn(() =>
Promise.resolve(user)
);
tmpAccountsPassword.passwordAuthenticator = jest.fn(() => Promise.resolve(user));
const ret = await tmpAccountsPassword.authenticate({

@@ -46,21 +46,5 @@ user: 'toto',

it('return user', async () => {
try {
const tmpAccountsPassword = new AccountsPassword({});
tmpAccountsPassword.passwordAuthenticator = jest.fn(() =>
Promise.resolve()
);
await tmpAccountsPassword.authenticate({
user: 'toto',
password: 'toto',
} as any);
throw new Error();
} catch (err) {
expect(err.message).toMatchSnapshot();
}
});
it('throws when user not found', async () => {
const findUserByEmail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -80,3 +64,3 @@ await password.authenticate({

const findPasswordHash = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail, findPasswordHash } as any;
password.setStore({ findUserByEmail, findPasswordHash } as any);
try {

@@ -96,3 +80,3 @@ await password.authenticate({

const findPasswordHash = jest.fn(() => Promise.resolve('hash'));
password.db = { findUserByEmail, findPasswordHash } as any;
password.setStore({ findUserByEmail, findPasswordHash } as any);
try {

@@ -113,3 +97,3 @@ await password.authenticate({

const findUserByEmail = jest.fn(() => Promise.resolve('user'));
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
const user = await password.findUserByEmail('email');

@@ -124,3 +108,3 @@ expect(findUserByEmail.mock.calls[0]).toMatchSnapshot();

const findUserByUsername = jest.fn(() => Promise.resolve('user'));
password.db = { findUserByUsername } as any;
password.setStore({ findUserByUsername } as any);
const user = await password.findUserByUsername('email');

@@ -135,3 +119,3 @@ expect(findUserByUsername.mock.calls[0]).toMatchSnapshot();

const addEmail = jest.fn(() => Promise.resolve());
password.db = { addEmail } as any;
password.setStore({ addEmail } as any);
await password.addEmail('id', 'email', true);

@@ -145,3 +129,3 @@ expect(addEmail.mock.calls[0]).toMatchSnapshot();

const removeEmail = jest.fn(() => Promise.resolve());
password.db = { removeEmail } as any;
password.setStore({ removeEmail } as any);
await password.removeEmail('id', 'email');

@@ -156,5 +140,3 @@ expect(removeEmail.mock.calls[0]).toMatchSnapshot();

const email = 'john.doe@gmail.com';
set(validUser, 'services.email.verificationTokens', [
{ token, address: email },
]);
set(validUser, 'services.email.verificationTokens', [{ token, address: email }]);
validUser.emails = [{ address: email }];

@@ -166,3 +148,3 @@ const invalidUser = { ...validUser };

const findUserByEmailVerificationToken = jest.fn(() => Promise.resolve());
password.db = { findUserByEmailVerificationToken } as any;
password.setStore({ findUserByEmailVerificationToken } as any);
try {

@@ -177,6 +159,4 @@ await password.verifyEmail(token);

it('throws when token not found', async () => {
const findUserByEmailVerificationToken = jest.fn(() =>
Promise.resolve({})
);
password.db = { findUserByEmailVerificationToken } as any;
const findUserByEmailVerificationToken = jest.fn(() => Promise.resolve({}));
password.setStore({ findUserByEmailVerificationToken } as any);
try {

@@ -191,6 +171,4 @@ await password.verifyEmail(token);

it('throws when email not found', async () => {
const findUserByEmailVerificationToken = jest.fn(() =>
Promise.resolve(invalidUser)
);
password.db = { findUserByEmailVerificationToken } as any;
const findUserByEmailVerificationToken = jest.fn(() => Promise.resolve(invalidUser));
password.setStore({ findUserByEmailVerificationToken } as any);
try {

@@ -205,7 +183,8 @@ await password.verifyEmail(token);

it('call this.db.verifyEmail', async () => {
const findUserByEmailVerificationToken = jest.fn(() =>
Promise.resolve(validUser)
);
const findUserByEmailVerificationToken = jest.fn(() => Promise.resolve(validUser));
const verifyEmail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmailVerificationToken, verifyEmail } as any;
password.setStore({
findUserByEmailVerificationToken,
verifyEmail,
} as any);
await password.verifyEmail(token);

@@ -230,3 +209,3 @@ expect(verifyEmail.mock.calls[0]).toMatchSnapshot();

const findUserByResetPasswordToken = jest.fn(() => Promise.resolve());
password.db = { findUserByResetPasswordToken } as any;
password.setStore({ findUserByResetPasswordToken } as any);
try {

@@ -241,7 +220,5 @@ await password.resetPassword(token, newPassword);

it('throws when token is expired', async () => {
const findUserByResetPasswordToken = jest.fn(() =>
Promise.resolve(invalidUser)
);
const findUserByResetPasswordToken = jest.fn(() => Promise.resolve(invalidUser));
const isTokenExpired = jest.fn(() => true);
password.db = { findUserByResetPasswordToken } as any;
password.setStore({ findUserByResetPasswordToken } as any);
password.server = { isTokenExpired } as any;

@@ -257,7 +234,5 @@ try {

it('throws when token have invalid email', async () => {
const findUserByResetPasswordToken = jest.fn(() =>
Promise.resolve(invalidUser)
);
const findUserByResetPasswordToken = jest.fn(() => Promise.resolve(invalidUser));
const isTokenExpired = jest.fn(() => false);
password.db = { findUserByResetPasswordToken } as any;
password.setStore({ findUserByResetPasswordToken } as any);
password.server = { isTokenExpired } as any;

@@ -273,13 +248,11 @@ try {

it('reset password and invalidate all sessions', async () => {
const findUserByResetPasswordToken = jest.fn(() =>
Promise.resolve(validUser)
);
const findUserByResetPasswordToken = jest.fn(() => Promise.resolve(validUser));
const isTokenExpired = jest.fn(() => false);
const setResetPassword = jest.fn(() => Promise.resolve());
const invalidateAllSessions = jest.fn(() => Promise.resolve());
password.db = {
password.setStore({
findUserByResetPasswordToken,
setResetPassword,
invalidateAllSessions,
} as any;
} as any);
password.server = { isTokenExpired } as any;

@@ -296,3 +269,3 @@ await password.resetPassword(token, newPassword);

const setPassword = jest.fn(() => Promise.resolve('user'));
password.db = { setPassword } as any;
password.setStore({ setPassword } as any);
const user = await password.setPassword(userId, 'new-password');

@@ -307,10 +280,20 @@ expect(setPassword.mock.calls[0][0]).toEqual(userId);

const email = 'john.doe@gmail.com';
const verifiedEmail = 'john.doe2@gmail.com';
const validUser = {
emails: [{ address: email }],
emails: [{ address: email }, { address: verifiedEmail, verified: true }],
};
const invalidUser = {};
it('throws if email is empty', async () => {
try {
await password.sendVerificationEmail('');
throw new Error();
} catch (err) {
expect(err.message).toMatchSnapshot();
}
});
it('throws if user is not found', async () => {
const findUserByEmail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -326,3 +309,3 @@ await password.sendVerificationEmail(email);

const findUserByEmail = jest.fn(() => Promise.resolve(invalidUser));
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -342,10 +325,10 @@ await password.sendVerificationEmail(email);

const sendMail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail, addEmailVerificationToken } as any;
password.setStore({ findUserByEmail, addEmailVerificationToken } as any);
password.server = {
prepareMail,
email: { sendMail },
options: { sendMail },
sanitizeUser,
} as any;
set(password.server, 'options.emailTemplates', {});
await password.sendVerificationEmail();
await password.sendVerificationEmail(verifiedEmail);
expect(addEmailVerificationToken.mock.calls[0].length).toBe(3);

@@ -362,6 +345,6 @@ expect(prepareMail.mock.calls[0].length).toBe(6);

const sendMail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail, addEmailVerificationToken } as any;
password.setStore({ findUserByEmail, addEmailVerificationToken } as any);
password.server = {
prepareMail,
email: { sendMail },
options: { sendMail },
sanitizeUser,

@@ -381,5 +364,14 @@ } as any;

it('throws if email is empty', async () => {
try {
await password.sendResetPasswordEmail('');
throw new Error();
} catch (err) {
expect(err.message).toMatchSnapshot();
}
});
it('throws if user is not found', async () => {
const findUserByEmail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -400,6 +392,6 @@ await password.sendResetPasswordEmail(email);

const getFirstUserEmail = jest.fn(() => Promise.resolve(email));
password.db = { findUserByEmail, addResetPasswordToken } as any;
password.setStore({ findUserByEmail, addResetPasswordToken } as any);
password.server = {
prepareMail,
email: { sendMail },
options: { sendMail },
sanitizeUser,

@@ -422,3 +414,3 @@ getFirstUserEmail,

const findUserByEmail = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -439,6 +431,6 @@ await password.sendEnrollmentEmail(email);

const getFirstUserEmail = jest.fn(() => Promise.resolve(email));
password.db = { findUserByEmail, addResetPasswordToken } as any;
password.setStore({ findUserByEmail, addResetPasswordToken } as any);
password.server = {
prepareMail,
email: { sendMail },
options: { sendMail },
sanitizeUser,

@@ -471,3 +463,3 @@ getFirstUserEmail,

const findUserByUsername = jest.fn(() => Promise.resolve('user'));
password.db = { findUserByUsername } as any;
password.setStore({ findUserByUsername } as any);
try {

@@ -486,3 +478,3 @@ await password.createUser({

const findUserByEmail = jest.fn(() => Promise.resolve('user'));
password.db = { findUserByEmail } as any;
password.setStore({ findUserByEmail } as any);
try {

@@ -504,3 +496,3 @@ await password.createUser({

const findUserByEmail = jest.fn(() => Promise.resolve());
tmpAccountsPassword.db = { findUserByEmail } as any;
tmpAccountsPassword.setStore({ findUserByEmail } as any);
try {

@@ -520,3 +512,3 @@ await tmpAccountsPassword.createUser({

const createUser = jest.fn(() => Promise.resolve());
password.db = { findUserByEmail, createUser } as any;
password.setStore({ findUserByEmail, createUser } as any);
await password.createUser({

@@ -523,0 +515,0 @@ password: '123456',

@@ -1,5 +0,10 @@

import { CreateUserType, UserObjectType, HashAlgorithm } from '@accounts/common';
import { PasswordCreateUserType, PasswordLoginType, PasswordType } from './types';
export declare const isEmail: (email?: string) => boolean;
import { CreateUser, User, DatabaseInterface, AuthenticationService } from '@accounts/types';
import { HashAlgorithm } from '@accounts/common';
import { TwoFactor, AccountsTwoFactorOptions } from '@accounts/two-factor';
import { AccountsServer } from '@accounts/server';
import { PasswordCreateUserType } from './types/password-create-user-type';
import { PasswordLoginType } from './types/password-login-type';
import { PasswordType } from './types/password-type';
export interface AccountsPasswordOptions {
twoFactor?: AccountsTwoFactorOptions;
passwordHashAlgorithm?: HashAlgorithm;

@@ -9,28 +14,105 @@ passwordResetTokenExpirationInDays?: number;

minimumPasswordLength?: number;
validateNewUser?: (user: CreateUserType) => Promise<boolean>;
validateNewUser?: (user: CreateUser) => Promise<boolean>;
validateEmail?(email?: string): boolean;
validatePassword?(email?: string): boolean;
validateUsername?(email?: string): boolean;
validatePassword?(password?: PasswordType): boolean;
validateUsername?(username?: string): boolean;
}
export default class AccountsPassword {
private serviceName;
export default class AccountsPassword implements AuthenticationService {
serviceName: string;
server: AccountsServer;
twoFactor: TwoFactor;
private options;
private db;
private server;
constructor(options: AccountsPasswordOptions);
authenticate(params: PasswordLoginType): Promise<UserObjectType>;
findUserByEmail(email: string): Promise<UserObjectType>;
findUserByUsername(username: string): Promise<UserObjectType>;
constructor(options?: AccountsPasswordOptions);
setStore(store: DatabaseInterface): void;
authenticate(params: PasswordLoginType): Promise<User>;
/**
* @description Find a user by one of his emails.
* @param {string} email - User email.
* @returns {Promise<Object>} - Return a user or null if not found.
*/
findUserByEmail(email: string): Promise<User | null>;
/**
* @description Find a user by his username.
* @param {string} username - User username.
* @returns {Promise<Object>} - Return a user or null if not found.
*/
findUserByUsername(username: string): Promise<User | null>;
/**
* @description Add an email address for a user.
* Use this instead of directly updating the database.
* @param {string} userId - User id.
* @param {string} newEmail - A new email address for the user.
* @param {boolean} [verified] - Whether the new email address should be marked as verified.
* Defaults to false.
* @returns {Promise<void>} - Return a Promise.
*/
addEmail(userId: string, newEmail: string, verified: boolean): Promise<void>;
/**
* @description Remove an email address for a user.
* Use this instead of directly updating the database.
* @param {string} userId - User id.
* @param {string} email - The email address to remove.
* @returns {Promise<void>} - Return a Promise.
*/
removeEmail(userId: string, email: string): Promise<void>;
/**
* @description Marks the user's email address as verified.
* @param {string} token - The token retrieved from the verification URL.
* @returns {Promise<void>} - Return a Promise.
*/
verifyEmail(token: string): Promise<void>;
/**
* @description Reset the password for a user using a token received in email.
* @param {string} token - The token retrieved from the reset password URL.
* @param {string} newPassword - A new password for the user.
* @returns {Promise<void>} - Return a Promise.
*/
resetPassword(token: string, newPassword: PasswordType): Promise<void>;
/**
* @description Change the password for a user.
* @param {string} userId - User id.
* @param {string} newPassword - A new password for the user.
* @returns {Promise<void>} - Return a Promise.
*/
setPassword(userId: string, newPassword: string): Promise<void>;
sendVerificationEmail(address?: string): Promise<void>;
/**
* @description Send an email with a link the user can use verify their email address.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first unverified email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
sendVerificationEmail(address: string): Promise<void>;
/**
* @description Send an email with a link the user can use to reset their password.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
sendResetPasswordEmail(address: string): Promise<void>;
/**
* @description Send an email with a link the user can use to set their initial password.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
sendEnrollmentEmail(address: string): Promise<void>;
/**
* @description Create a new user.
* @param user - The user object.
* @returns Return the id of user created.
*/
createUser(user: PasswordCreateUserType): Promise<string>;
private passwordAuthenticator(user, password);
private hashAndBcryptPassword(password);
/**
* Given a username, user and/or email figure out the username and/or email.
*
* @param user An object containing at least `username`, `user` and/or `email`.
* @returns An object containing `id`, `username` and `email`.
*/
private toUsernameAndEmail({user, username, email, id});
}

@@ -47,9 +47,6 @@ "use strict";

var lodash_1 = require("lodash");
var two_factor_1 = require("@accounts/two-factor");
var server_1 = require("@accounts/server");
var utils_1 = require("@accounts/server/lib/utils");
var encryption_1 = require("./encryption");
exports.isEmail = function (email) {
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return email && re.test(email);
};
var encryption_1 = require("./utils/encryption");
var isEmail_1 = require("./utils/isEmail");
var defaultOptions = {

@@ -60,3 +57,3 @@ passwordResetTokenExpirationInDays: 3,

validateEmail: function (email) {
var isValid = !lodash_1.isEmpty(lodash_1.trim(email || '')) && exports.isEmail(email);
var isValid = !lodash_1.isEmpty(lodash_1.trim(email || '')) && isEmail_1.isEmail(email);
return Boolean(isValid);

@@ -74,13 +71,20 @@ },

};
var AccountsPassword = (function () {
var AccountsPassword = /** @class */ (function () {
function AccountsPassword(options) {
if (options === void 0) { options = {}; }
this.serviceName = 'password';
this.options = __assign({}, defaultOptions, options);
this.twoFactor = new two_factor_1.TwoFactor(options.twoFactor);
}
AccountsPassword.prototype.setStore = function (store) {
this.db = store;
this.twoFactor.setStore(store);
};
AccountsPassword.prototype.authenticate = function (params) {
return __awaiter(this, void 0, void 0, function () {
var user, password, foundUser;
var user, password, code, foundUser;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
user = params.user, password = params.password;
user = params.user, password = params.password, code = params.code;
if (!user || !password) {

@@ -92,9 +96,11 @@ throw new Error('Unrecognized options for login request');

}
return [4, this.passwordAuthenticator(user, password)];
return [4 /*yield*/, this.passwordAuthenticator(user, password)];
case 1:
foundUser = _a.sent();
if (!foundUser) {
throw new Error('User not found');
}
return [2, foundUser];
if (!this.twoFactor.getUserService(foundUser)) return [3 /*break*/, 3];
return [4 /*yield*/, this.twoFactor.authenticate(foundUser, code)];
case 2:
_a.sent();
_a.label = 3;
case 3: return [2 /*return*/, foundUser];
}

@@ -104,14 +110,46 @@ });

};
/**
* @description Find a user by one of his emails.
* @param {string} email - User email.
* @returns {Promise<Object>} - Return a user or null if not found.
*/
AccountsPassword.prototype.findUserByEmail = function (email) {
return this.db.findUserByEmail(email);
};
/**
* @description Find a user by his username.
* @param {string} username - User username.
* @returns {Promise<Object>} - Return a user or null if not found.
*/
AccountsPassword.prototype.findUserByUsername = function (username) {
return this.db.findUserByUsername(username);
};
/**
* @description Add an email address for a user.
* Use this instead of directly updating the database.
* @param {string} userId - User id.
* @param {string} newEmail - A new email address for the user.
* @param {boolean} [verified] - Whether the new email address should be marked as verified.
* Defaults to false.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.addEmail = function (userId, newEmail, verified) {
// TODO use this.options.verifyEmail before
return this.db.addEmail(userId, newEmail, verified);
};
/**
* @description Remove an email address for a user.
* Use this instead of directly updating the database.
* @param {string} userId - User id.
* @param {string} email - The email address to remove.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.removeEmail = function (userId, email) {
return this.db.removeEmail(userId, email);
};
/**
* @description Marks the user's email address as verified.
* @param {string} token - The token retrieved from the verification URL.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.verifyEmail = function (token) {

@@ -122,3 +160,3 @@ return __awaiter(this, void 0, void 0, function () {

switch (_a.label) {
case 0: return [4, this.db.findUserByEmailVerificationToken(token)];
case 0: return [4 /*yield*/, this.db.findUserByEmailVerificationToken(token)];
case 1:

@@ -138,6 +176,6 @@ user = _a.sent();

}
return [4, this.db.verifyEmail(user.id, emailRecord.address)];
return [4 /*yield*/, this.db.verifyEmail(user.id, emailRecord.address)];
case 2:
_a.sent();
return [2];
return [2 /*return*/];
}

@@ -147,2 +185,8 @@ });

};
/**
* @description Reset the password for a user using a token received in email.
* @param {string} token - The token retrieved from the reset password URL.
* @param {string} newPassword - A new password for the user.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.resetPassword = function (token, newPassword) {

@@ -153,3 +197,3 @@ return __awaiter(this, void 0, void 0, function () {

switch (_a.label) {
case 0: return [4, this.db.findUserByResetPasswordToken(token)];
case 0: return [4 /*yield*/, this.db.findUserByResetPasswordToken(token)];
case 1:

@@ -169,10 +213,13 @@ user = _a.sent();

}
return [4, this.hashAndBcryptPassword(newPassword)];
return [4 /*yield*/, this.hashAndBcryptPassword(newPassword)];
case 2:
password = _a.sent();
return [4, this.db.setResetPassword(user.id, resetTokenRecord.address, password, token)];
// Change the user password and remove the old token
return [4 /*yield*/, this.db.setResetPassword(user.id, resetTokenRecord.address, password, token)];
case 3:
// Change the user password and remove the old token
_a.sent();
// Changing the password should invalidate existing sessions
this.db.invalidateAllSessions(user.id);
return [2];
return [2 /*return*/];
}

@@ -182,2 +229,8 @@ });

};
/**
* @description Change the password for a user.
* @param {string} userId - User id.
* @param {string} newPassword - A new password for the user.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.setPassword = function (userId, newPassword) {

@@ -188,6 +241,6 @@ return __awaiter(this, void 0, void 0, function () {

switch (_a.label) {
case 0: return [4, encryption_1.bcryptPassword(newPassword)];
case 0: return [4 /*yield*/, encryption_1.bcryptPassword(newPassword)];
case 1:
password = _a.sent();
return [2, this.db.setPassword(userId, password)];
return [2 /*return*/, this.db.setPassword(userId, password)];
}

@@ -197,8 +250,19 @@ });

};
/**
* @description Send an email with a link the user can use verify their email address.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first unverified email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.sendVerificationEmail = function (address) {
return __awaiter(this, void 0, void 0, function () {
var user, email, emails, token, resetPasswordMail;
var user, emails, token, resetPasswordMail;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4, this.db.findUserByEmail(address)];
case 0:
if (!address) {
throw new Error('Invalid email');
}
return [4 /*yield*/, this.db.findUserByEmail(address)];
case 1:

@@ -209,6 +273,2 @@ user = _a.sent();

}
if (!address) {
email = lodash_1.find(user.emails, function (e) { return !e.verified; });
address = email && email.address;
}
emails = user.emails || [];

@@ -219,10 +279,10 @@ if (!address || !lodash_1.includes(emails.map(function (email) { return email.address; }), address)) {

token = server_1.generateRandomToken();
return [4, this.db.addEmailVerificationToken(user.id, address, token)];
return [4 /*yield*/, this.db.addEmailVerificationToken(user.id, address, token)];
case 2:
_a.sent();
resetPasswordMail = this.server.prepareMail(address, token, this.server.sanitizeUser(user), 'verify-email', this.server.options.emailTemplates.verifyEmail, this.server.options.emailTemplates.from);
return [4, this.server.email.sendMail(resetPasswordMail)];
return [4 /*yield*/, this.server.options.sendMail(resetPasswordMail)];
case 3:
_a.sent();
return [2];
return [2 /*return*/];
}

@@ -232,2 +292,9 @@ });

};
/**
* @description Send an email with a link the user can use to reset their password.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.sendResetPasswordEmail = function (address) {

@@ -238,3 +305,7 @@ return __awaiter(this, void 0, void 0, function () {

switch (_a.label) {
case 0: return [4, this.db.findUserByEmail(address)];
case 0:
if (!address) {
throw new Error('Invalid email');
}
return [4 /*yield*/, this.db.findUserByEmail(address)];
case 1:

@@ -245,12 +316,12 @@ user = _a.sent();

}
address = utils_1.getFirstUserEmail(user, address);
address = server_1.getFirstUserEmail(user, address);
token = server_1.generateRandomToken();
return [4, this.db.addResetPasswordToken(user.id, address, token)];
return [4 /*yield*/, this.db.addResetPasswordToken(user.id, address, token)];
case 2:
_a.sent();
resetPasswordMail = this.server.prepareMail(address, token, this.server.sanitizeUser(user), 'reset-password', this.server.options.emailTemplates.resetPassword, this.server.options.emailTemplates.from);
return [4, this.server.email.sendMail(resetPasswordMail)];
return [4 /*yield*/, this.server.options.sendMail(resetPasswordMail)];
case 3:
_a.sent();
return [2];
return [2 /*return*/];
}

@@ -260,2 +331,9 @@ });

};
/**
* @description Send an email with a link the user can use to set their initial password.
* @param {string} [address] - Which address of the user's to send the email to.
* This address must be in the user's emails list.
* Defaults to the first email in the list.
* @returns {Promise<void>} - Return a Promise.
*/
AccountsPassword.prototype.sendEnrollmentEmail = function (address) {

@@ -266,3 +344,3 @@ return __awaiter(this, void 0, void 0, function () {

switch (_a.label) {
case 0: return [4, this.db.findUserByEmail(address)];
case 0: return [4 /*yield*/, this.db.findUserByEmail(address)];
case 1:

@@ -273,12 +351,12 @@ user = _a.sent();

}
address = utils_1.getFirstUserEmail(user, address);
address = server_1.getFirstUserEmail(user, address);
token = server_1.generateRandomToken();
return [4, this.db.addResetPasswordToken(user.id, address, token, 'enroll')];
return [4 /*yield*/, this.db.addResetPasswordToken(user.id, address, token, 'enroll')];
case 2:
_a.sent();
enrollmentMail = this.server.prepareMail(address, token, this.server.sanitizeUser(user), 'enroll-account', this.server.options.emailTemplates.enrollAccount, this.server.options.emailTemplates.from);
return [4, this.server.email.sendMail(enrollmentMail)];
return [4 /*yield*/, this.server.options.sendMail(enrollmentMail)];
case 3:
_a.sent();
return [2];
return [2 /*return*/];
}

@@ -288,2 +366,7 @@ });

};
/**
* @description Create a new user.
* @param user - The user object.
* @returns Return the id of user created.
*/
AccountsPassword.prototype.createUser = function (user) {

@@ -295,9 +378,8 @@ return __awaiter(this, void 0, void 0, function () {

case 0:
if (!this.options.validateUsername(user.username) &&
!this.options.validateEmail(user.email)) {
if (!this.options.validateUsername(user.username) && !this.options.validateEmail(user.email)) {
throw new Error('Username or Email is required');
}
_a = user.username;
if (!_a) return [3, 2];
return [4, this.db.findUserByUsername(user.username)];
if (!_a) return [3 /*break*/, 2];
return [4 /*yield*/, this.db.findUserByUsername(user.username)];
case 1:

@@ -311,4 +393,4 @@ _a = (_d.sent());

_b = user.email;
if (!_b) return [3, 4];
return [4, this.db.findUserByEmail(user.email)];
if (!_b) return [3 /*break*/, 4];
return [4 /*yield*/, this.db.findUserByEmail(user.email)];
case 3:

@@ -321,4 +403,7 @@ _b = (_d.sent());

}
if (!user.password) return [3, 6];
return [4, this.hashAndBcryptPassword(user.password)];
if (!user.password) return [3 /*break*/, 6];
if (!this.options.validatePassword(user.password)) {
throw new Error('Invalid password');
}
return [4 /*yield*/, this.hashAndBcryptPassword(user.password)];
case 5:

@@ -336,4 +421,4 @@ password = _d.sent();

_c = lodash_1.isFunction(validateNewUser);
if (!_c) return [3, 8];
return [4, validateNewUser(proposedUserObject)];
if (!_c) return [3 /*break*/, 8];
return [4 /*yield*/, validateNewUser(proposedUserObject)];
case 7:

@@ -346,3 +431,3 @@ _c = !(_d.sent());

}
return [2, this.db.createUser(proposedUserObject)];
return [2 /*return*/, this.db.createUser(proposedUserObject)];
}

@@ -361,17 +446,20 @@ });

: this.toUsernameAndEmail(__assign({}, user)), username = _a.username, email = _a.email, id = _a.id;
if (!id) return [3, 2];
return [4, this.db.findUserById(id)];
if (!id) return [3 /*break*/, 2];
return [4 /*yield*/, this.db.findUserById(id)];
case 1:
// this._validateLoginWithField('id', user);
foundUser = _b.sent();
return [3, 6];
return [3 /*break*/, 6];
case 2:
if (!username) return [3, 4];
return [4, this.db.findUserByUsername(username)];
if (!username) return [3 /*break*/, 4];
return [4 /*yield*/, this.db.findUserByUsername(username)];
case 3:
// this._validateLoginWithField('username', user);
foundUser = _b.sent();
return [3, 6];
return [3 /*break*/, 6];
case 4:
if (!email) return [3, 6];
return [4, this.db.findUserByEmail(email)];
if (!email) return [3 /*break*/, 6];
return [4 /*yield*/, this.db.findUserByEmail(email)];
case 5:
// this._validateLoginWithField('email', user);
foundUser = _b.sent();

@@ -383,3 +471,3 @@ _b.label = 6;

}
return [4, this.db.findPasswordHash(foundUser.id)];
return [4 /*yield*/, this.db.findPasswordHash(foundUser.id)];
case 7:

@@ -391,6 +479,4 @@ hash = _b.sent();

hashAlgorithm = this.options.passwordHashAlgorithm;
pass = hashAlgorithm
? encryption_1.hashPassword(password, hashAlgorithm)
: password;
return [4, encryption_1.verifyPassword(pass, hash)];
pass = hashAlgorithm ? encryption_1.hashPassword(password, hashAlgorithm) : password;
return [4 /*yield*/, encryption_1.verifyPassword(pass, hash)];
case 8:

@@ -401,3 +487,3 @@ isPasswordValid = _b.sent();

}
return [2, foundUser];
return [2 /*return*/, foundUser];
}

@@ -412,13 +498,17 @@ });

hashAlgorithm = this.options.passwordHashAlgorithm;
hashedPassword = hashAlgorithm
? encryption_1.hashPassword(password, hashAlgorithm)
: password;
return [2, encryption_1.bcryptPassword(hashedPassword)];
hashedPassword = hashAlgorithm ? encryption_1.hashPassword(password, hashAlgorithm) : password;
return [2 /*return*/, encryption_1.bcryptPassword(hashedPassword)];
});
});
};
/**
* Given a username, user and/or email figure out the username and/or email.
*
* @param user An object containing at least `username`, `user` and/or `email`.
* @returns An object containing `id`, `username` and `email`.
*/
AccountsPassword.prototype.toUsernameAndEmail = function (_a) {
var user = _a.user, username = _a.username, email = _a.email, id = _a.id;
if (user && !username && !email) {
if (exports.isEmail(user)) {
if (isEmail_1.isEmail(user)) {
email = user;

@@ -425,0 +515,0 @@ username = null;

import AccountsPassword from './accounts-password';
export default AccountsPassword;
export { AccountsPassword };

@@ -5,2 +5,3 @@ "use strict";

exports.AccountsPassword = accounts_password_1.default;
exports.default = accounts_password_1.default;
//# sourceMappingURL=index.js.map
{
"name": "@accounts/password",
"version": "0.1.0-alpha.b5d6f4ab",
"version": "0.1.0-alpha.cdeba60e",
"license": "MIT",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"scripts": {

@@ -11,3 +12,4 @@ "clean": "rimraf lib",

"compile": "tsc",
"prepublish": "npm run compile",
"prepublishOnly": "npm run compile",
"test": "npm run test",
"testonly": "jest --coverage",

@@ -24,6 +26,6 @@ "coverage": "jest --coverage"

"js"
],
"mapCoverage": true
]
},
"dependencies": {
"@accounts/two-factor": "^0.1.0-alpha.cdeba60e",
"bcryptjs": "^2.4.3",

@@ -33,11 +35,13 @@ "lodash": "^4.17.4"

"devDependencies": {
"@accounts/common": "^0.1.0-alpha.b5d6f4ab",
"@accounts/server": "^0.1.0-alpha.b5d6f4ab",
"@types/bcryptjs": "^2.4.0",
"@types/lodash": "^4.14.66"
"@accounts/common": "^0.1.0-alpha.cdeba60e",
"@accounts/server": "^0.1.0-alpha.cdeba60e",
"@accounts/types": "^0.1.0-alpha.cdeba60e",
"@types/bcryptjs": "2.4.1",
"@types/lodash": "4.14.106",
"rimraf": "2.6.2"
},
"peerDependencies": {
"@accounts/common": "^0.1.0-alpha.b5d6f4ab",
"@accounts/server": "^0.1.0-alpha.b5d6f4ab"
"@accounts/common": "^0.1.0-beta.0",
"@accounts/server": "^0.1.0-beta.0"
}
}

@@ -1,38 +0,16 @@

import {
trim,
isEmpty,
isFunction,
isString,
isPlainObject,
get,
find,
includes,
} from 'lodash';
import {
CreateUserType,
UserObjectType,
HashAlgorithm,
LoginUserIdentityType,
EmailRecord,
TokenRecord,
} from '@accounts/common';
import {
DBInterface,
AccountsServer,
generateRandomToken,
} from '@accounts/server';
import { getFirstUserEmail } from '@accounts/server/lib/utils';
import { hashPassword, bcryptPassword, verifyPassword } from './encryption';
import {
PasswordCreateUserType,
PasswordLoginType,
PasswordType,
} from './types';
import { trim, isEmpty, isFunction, isString, isPlainObject, get, find, includes } from 'lodash';
import { CreateUser, User, Login, EmailRecord, TokenRecord, DatabaseInterface, AuthenticationService } from '@accounts/types';
import { HashAlgorithm } from '@accounts/common';
import { TwoFactor, AccountsTwoFactorOptions } from '@accounts/two-factor';
import { AccountsServer, generateRandomToken, getFirstUserEmail } from '@accounts/server';
import { hashPassword, bcryptPassword, verifyPassword } from './utils/encryption';
export const isEmail = (email?: string) => {
const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return email && re.test(email);
};
import { PasswordCreateUserType } from './types/password-create-user-type';
import { PasswordLoginType } from './types/password-login-type';
import { PasswordType } from './types/password-type';
import { isEmail } from './utils/isEmail';
export interface AccountsPasswordOptions {
twoFactor?: AccountsTwoFactorOptions;
passwordHashAlgorithm?: HashAlgorithm;

@@ -42,6 +20,6 @@ passwordResetTokenExpirationInDays?: number;

minimumPasswordLength?: number;
validateNewUser?: (user: CreateUserType) => Promise<boolean>;
validateNewUser?: (user: CreateUser) => Promise<boolean>;
validateEmail?(email?: string): boolean;
validatePassword?(email?: string): boolean;
validateUsername?(email?: string): boolean;
validatePassword?(password?: PasswordType): boolean;
validateUsername?(username?: string): boolean;
}

@@ -57,3 +35,3 @@

},
validatePassword(password?: string): boolean {
validatePassword(password?: PasswordType): boolean {
const isValid = !isEmpty(password);

@@ -64,4 +42,3 @@ return isValid;

const usernameRegex = /^[a-zA-Z][a-zA-Z0-9]*$/;
const isValid =
username && !isEmpty(trim(username)) && usernameRegex.test(username);
const isValid = username && !isEmpty(trim(username)) && usernameRegex.test(username);
return Boolean(isValid);

@@ -71,16 +48,21 @@ },

export default class AccountsPassword {
private serviceName: string;
export default class AccountsPassword implements AuthenticationService {
public serviceName = 'password';
public server: AccountsServer;
public twoFactor: TwoFactor;
private options: AccountsPasswordOptions;
private db: DBInterface;
private server: AccountsServer;
private db: DatabaseInterface;
constructor(options: AccountsPasswordOptions) {
constructor(options: AccountsPasswordOptions = {}) {
this.options = { ...defaultOptions, ...options };
this.twoFactor = new TwoFactor(options.twoFactor);
}
public async authenticate(
params: PasswordLoginType
): Promise<UserObjectType> {
const { user, password } = params;
public setStore(store: DatabaseInterface) {
this.db = store;
this.twoFactor.setStore(store);
}
public async authenticate(params: PasswordLoginType): Promise<User> {
const { user, password, code } = params;
if (!user || !password) {

@@ -93,14 +75,7 @@ throw new Error('Unrecognized options for login request');

let foundUser;
/* if (this._options.passwordAuthenticator) {
foundUser = await this._externalPasswordAuthenticator(
this._options.passwordAuthenticator,
user,
password
);
} else { */
foundUser = await this.passwordAuthenticator(user, password);
const foundUser = await this.passwordAuthenticator(user, password);
if (!foundUser) {
throw new Error('User not found');
// If user activated two factor authentication try with the code
if (this.twoFactor.getUserService(foundUser)) {
await this.twoFactor.authenticate(foundUser, code);
}

@@ -116,3 +91,3 @@

*/
public findUserByEmail(email: string): Promise<UserObjectType> {
public findUserByEmail(email: string): Promise<User | null> {
return this.db.findUserByEmail(email);

@@ -126,3 +101,3 @@ }

*/
public findUserByUsername(username: string): Promise<UserObjectType> {
public findUserByUsername(username: string): Promise<User | null> {
return this.db.findUserByUsername(username);

@@ -140,7 +115,4 @@ }

*/
public addEmail(
userId: string,
newEmail: string,
verified: boolean
): Promise<void> {
public addEmail(userId: string, newEmail: string, verified: boolean): Promise<void> {
// TODO use this.options.verifyEmail before
return this.db.addEmail(userId, newEmail, verified);

@@ -176,6 +148,3 @@ }

);
const tokenRecord = find(
verificationTokens,
(t: TokenRecord) => t.token === token
);
const tokenRecord = find(verificationTokens, (t: TokenRecord) => t.token === token);
if (!tokenRecord) {

@@ -185,6 +154,3 @@ throw new Error('Verify email link expired');

// TODO check time for expiry date
const emailRecord = find(
user.emails,
(e: EmailRecord) => e.address === tokenRecord.address
);
const emailRecord = find(user.emails, (e: EmailRecord) => e.address === tokenRecord.address);
if (!emailRecord) {

@@ -202,6 +168,3 @@ throw new Error('Verify email link is for unknown address');

*/
public async resetPassword(
token: string,
newPassword: PasswordType
): Promise<void> {
public async resetPassword(token: string, newPassword: PasswordType): Promise<void> {
const user = await this.db.findUserByResetPasswordToken(token);

@@ -221,8 +184,3 @@ if (!user) {

const emails = user.emails || [];
if (
!includes(
emails.map((email: EmailRecord) => email.address),
resetTokenRecord.address
)
) {
if (!includes(emails.map((email: EmailRecord) => email.address), resetTokenRecord.address)) {
throw new Error('Token has invalid email address');

@@ -233,8 +191,3 @@ }

// Change the user password and remove the old token
await this.db.setResetPassword(
user.id,
resetTokenRecord.address,
password,
token
);
await this.db.setResetPassword(user.id, resetTokenRecord.address, password, token);
// Changing the password should invalidate existing sessions

@@ -262,3 +215,6 @@ this.db.invalidateAllSessions(user.id);

*/
public async sendVerificationEmail(address?: string): Promise<void> {
public async sendVerificationEmail(address: string): Promise<void> {
if (!address) {
throw new Error('Invalid email');
}
const user = await this.db.findUserByEmail(address);

@@ -268,7 +224,2 @@ if (!user) {

}
// If no address provided find the first unverified email
if (!address) {
const email = find(user.emails, e => !e.verified);
address = email && email.address;
}
// Make sure the address is valid

@@ -291,3 +242,3 @@ const emails = user.emails || [];

await this.server.email.sendMail(resetPasswordMail);
await this.server.options.sendMail(resetPasswordMail);
}

@@ -303,2 +254,5 @@

public async sendResetPasswordEmail(address: string): Promise<void> {
if (!address) {
throw new Error('Invalid email');
}
const user = await this.db.findUserByEmail(address);

@@ -321,3 +275,3 @@ if (!user) {

await this.server.email.sendMail(resetPasswordMail);
await this.server.options.sendMail(resetPasswordMail);
}

@@ -350,3 +304,3 @@

await this.server.email.sendMail(enrollmentMail);
await this.server.options.sendMail(enrollmentMail);
}

@@ -360,6 +314,3 @@

public async createUser(user: PasswordCreateUserType): Promise<string> {
if (
!this.options.validateUsername(user.username) &&
!this.options.validateEmail(user.email)
) {
if (!this.options.validateUsername(user.username) && !this.options.validateEmail(user.email)) {
throw new Error('Username or Email is required');

@@ -378,2 +329,5 @@ }

if (user.password) {
if (!this.options.validatePassword(user.password)) {
throw new Error('Invalid password');
}
password = await this.hashAndBcryptPassword(user.password);

@@ -390,6 +344,3 @@ }

const { validateNewUser } = this.options;
if (
isFunction(validateNewUser) &&
!await validateNewUser(proposedUserObject)
) {
if (isFunction(validateNewUser) && !await validateNewUser(proposedUserObject)) {
throw new Error('User invalid');

@@ -402,5 +353,5 @@ }

private async passwordAuthenticator(
user: string | LoginUserIdentityType,
user: string | Login,
password: PasswordType
): Promise<any> {
): Promise<User> {
const { username, email, id } = isString(user)

@@ -410,3 +361,3 @@ ? this.toUsernameAndEmail({ user })

let foundUser;
let foundUser: User;

@@ -434,5 +385,3 @@ if (id) {

const hashAlgorithm = this.options.passwordHashAlgorithm;
const pass: any = hashAlgorithm
? hashPassword(password, hashAlgorithm)
: password;
const pass: any = hashAlgorithm ? hashPassword(password, hashAlgorithm) : password;
const isPasswordValid = await verifyPassword(pass, hash);

@@ -449,5 +398,3 @@

const hashAlgorithm = this.options.passwordHashAlgorithm;
const hashedPassword: any = hashAlgorithm
? hashPassword(password, hashAlgorithm)
: password;
const hashedPassword: any = hashAlgorithm ? hashPassword(password, hashAlgorithm) : password;
return bcryptPassword(hashedPassword);

@@ -454,0 +401,0 @@ }

import AccountsPassword from './accounts-password';
export default AccountsPassword;
export { AccountsPassword };

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc