@accounts/server
Advanced tools
Comparing version 0.1.0-alpha.5b339220 to 0.1.0-alpha.6732aa8e
/// <reference types="@types/node" /> | ||
import { EventEmitter } from 'events'; | ||
import { UserObjectType, LoginReturnType, TokensType, SessionType, ImpersonateReturnType, HookListener } from '@accounts/common'; | ||
import { EmailConnector, EmailTemplateType } from './email'; | ||
import { AccountsServerOptions, ConnectionInformationsType } from './types'; | ||
import { UserObjectType, CreateUserType, PasswordLoginUserType, LoginReturnType, TokensType, SessionType, ImpersonateReturnType, PasswordType, HookListener } from '@accounts/common'; | ||
import { AccountsServerConfiguration, PasswordAuthenticator } from './config'; | ||
import { DBInterface } from './db-interface'; | ||
export interface TokenRecord { | ||
@@ -28,10 +28,9 @@ token: string; | ||
export declare class AccountsServer { | ||
options: AccountsServerOptions; | ||
email: EmailConnector; | ||
private services; | ||
private _options; | ||
private db; | ||
private email; | ||
private emailTemplates; | ||
private hooks; | ||
constructor(options: AccountsServerOptions, services: any); | ||
getServices(): any; | ||
getOptions(): AccountsServerOptions; | ||
config(options: AccountsServerConfiguration, db: DBInterface): void; | ||
options(): AccountsServerConfiguration; | ||
onLoginSuccess(callback: HookListener): RemoveListnerHandle; | ||
@@ -49,4 +48,6 @@ onLoginError(callback: HookListener): RemoveListnerHandle; | ||
onImpersonationError(callback: HookListener): RemoveListnerHandle; | ||
loginWithService(serviceName: string, params: any, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
loginWithUser(user: UserObjectType, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
loginWithPassword(user: PasswordLoginUserType, password: PasswordType, ip: string, userAgent: string): Promise<LoginReturnType>; | ||
_externalPasswordAuthenticator(authFn: PasswordAuthenticator, user: PasswordLoginUserType, password: PasswordType): Promise<any>; | ||
loginWithUser(user: UserObjectType, ip?: string, userAgent?: string): Promise<LoginReturnType>; | ||
createUser(user: CreateUserType): Promise<string>; | ||
impersonate(accessToken: string, username: string, ip: string, userAgent: string): Promise<ImpersonateReturnType>; | ||
@@ -58,13 +59,28 @@ refreshTokens(accessToken: string, refreshToken: string, ip: string, userAgent: string): Promise<LoginReturnType>; | ||
findSessionByAccessToken(accessToken: string): Promise<SessionType>; | ||
findUserByEmail(email: string): Promise<UserObjectType>; | ||
findUserByUsername(username: string): Promise<UserObjectType>; | ||
findUserById(userId: string): Promise<UserObjectType>; | ||
addEmail(userId: string, newEmail: string, verified: boolean): Promise<void>; | ||
removeEmail(userId: string, email: string): Promise<void>; | ||
verifyEmail(token: string): Promise<void>; | ||
resetPassword(token: string, newPassword: PasswordType): Promise<void>; | ||
setPassword(userId: string, newPassword: string): Promise<void>; | ||
setProfile(userId: string, profile: object): Promise<void>; | ||
updateProfile(userId: string, profile: object): Promise<object>; | ||
on(eventName: string, callback: HookListener): RemoveListnerHandle; | ||
isTokenExpired(token: string, tokenRecord?: TokenRecord): boolean; | ||
prepareMail(to: string, token: string, user: UserObjectType, pathFragment: string, emailTemplate: EmailTemplateType, from: string): any; | ||
sanitizeUser(user: UserObjectType): UserObjectType; | ||
private internalUserSanitizer(user); | ||
private defaultPrepareEmail(to, token, user, pathFragment, emailTemplate, from); | ||
private defaultCreateTokenizedUrl(pathFragment, token); | ||
sendVerificationEmail(address: string): Promise<void>; | ||
sendResetPasswordEmail(address: string): Promise<void>; | ||
sendEnrollmentEmail(address: string): Promise<void>; | ||
private _on(eventName, callback); | ||
private _isTokenExpired(token, tokenRecord?); | ||
private _internalUserSanitizer(user); | ||
private _sanitizeUser(user); | ||
private _prepareMail(to, token, user, pathFragment, emailTemplate, from); | ||
private _defaultPrepareEmail(to, token, user, pathFragment, emailTemplate, from); | ||
private _defaultCreateTokenizedUrl(pathFragment, token); | ||
private _getFirstUserEmail(user, address); | ||
private _hashAndBcryptPassword(password); | ||
private _validateLoginWithField(fieldName, user); | ||
private _defaultPasswordAuthenticator(user, password); | ||
} | ||
export default AccountsServer; | ||
declare const _default: AccountsServer; | ||
export default _default; |
@@ -49,2 +49,8 @@ "use strict"; | ||
var isString = require("lodash/isString"); | ||
var isPlainObject = require("lodash/isPlainObject"); | ||
var isFunction = require("lodash/isFunction"); | ||
var isArray = require("lodash/isArray"); | ||
var find = require("lodash/find"); | ||
var includes = require("lodash/includes"); | ||
var get = require("lodash/get"); | ||
var events_1 = require("events"); | ||
@@ -54,17 +60,6 @@ var jwt = require("jsonwebtoken"); | ||
var config_1 = require("./config"); | ||
var encryption_1 = require("./encryption"); | ||
var tokens_1 = require("./tokens"); | ||
var email_1 = require("./email"); | ||
var defaultOptions = { | ||
tokenSecret: 'secret', | ||
tokenConfigs: { | ||
accessToken: { | ||
expiresIn: '90m', | ||
}, | ||
refreshToken: { | ||
expiresIn: '7d', | ||
}, | ||
}, | ||
emailTemplates: email_1.emailTemplates, | ||
userObjectSanitizer: function (user) { return user; }, | ||
}; | ||
var email_templates_1 = require("./email-templates"); | ||
exports.ServerHooks = { | ||
@@ -85,76 +80,94 @@ LoginSuccess: 'LoginSuccess', | ||
var AccountsServer = (function () { | ||
function AccountsServer(options, services) { | ||
this.options = __assign({}, defaultOptions, options); | ||
if (!this.options.db) { | ||
function AccountsServer() { | ||
} | ||
AccountsServer.prototype.config = function (options, db) { | ||
this._options = __assign({}, config_1.default, options); | ||
if (!db) { | ||
throw new common_1.AccountsError('A database driver is required'); | ||
} | ||
this.services = services; | ||
this.db = this.options.db; | ||
for (var service in this.services) { | ||
this.services[service].db = this.db; | ||
this.services[service].server = this; | ||
this.db = db; | ||
this.email = this._options.sendMail | ||
? { sendMail: this._options.sendMail } | ||
: new email_1.default(this._options.email); | ||
this.emailTemplates = email_templates_1.default; | ||
if (!this.hooks) { | ||
this.hooks = new events_1.EventEmitter(); | ||
} | ||
this.email = this.options.sendMail | ||
? { sendMail: this.options.sendMail } | ||
: new email_1.default(this.options.email); | ||
this.hooks = new events_1.EventEmitter(); | ||
} | ||
AccountsServer.prototype.getServices = function () { | ||
return this.services; | ||
}; | ||
AccountsServer.prototype.getOptions = function () { | ||
return this.options; | ||
AccountsServer.prototype.options = function () { | ||
return this._options; | ||
}; | ||
AccountsServer.prototype.onLoginSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.LoginSuccess, callback); | ||
return this._on(exports.ServerHooks.LoginSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onLoginError = function (callback) { | ||
return this.on(exports.ServerHooks.LoginError, callback); | ||
return this._on(exports.ServerHooks.LoginError, callback); | ||
}; | ||
AccountsServer.prototype.onLogoutSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.LogoutSuccess, callback); | ||
return this._on(exports.ServerHooks.LogoutSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onLogoutError = function (callback) { | ||
return this.on(exports.ServerHooks.LogoutError, callback); | ||
return this._on(exports.ServerHooks.LogoutError, callback); | ||
}; | ||
AccountsServer.prototype.onCreateUserSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.CreateUserSuccess, callback); | ||
return this._on(exports.ServerHooks.CreateUserSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onCreateUserError = function (callback) { | ||
return this.on(exports.ServerHooks.CreateUserError, callback); | ||
return this._on(exports.ServerHooks.CreateUserError, callback); | ||
}; | ||
AccountsServer.prototype.onResumeSessionSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.ResumeSessionSuccess, callback); | ||
return this._on(exports.ServerHooks.ResumeSessionSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onResumeSessionError = function (callback) { | ||
return this.on(exports.ServerHooks.ResumeSessionError, callback); | ||
return this._on(exports.ServerHooks.ResumeSessionError, callback); | ||
}; | ||
AccountsServer.prototype.onRefreshTokensSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.RefreshTokensSuccess, callback); | ||
return this._on(exports.ServerHooks.RefreshTokensSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onRefreshTokensError = function (callback) { | ||
return this.on(exports.ServerHooks.RefreshTokensError, callback); | ||
return this._on(exports.ServerHooks.RefreshTokensError, callback); | ||
}; | ||
AccountsServer.prototype.onImpersonationSuccess = function (callback) { | ||
return this.on(exports.ServerHooks.ImpersonationSuccess, callback); | ||
return this._on(exports.ServerHooks.ImpersonationSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onImpersonationError = function (callback) { | ||
return this.on(exports.ServerHooks.ImpersonationError, callback); | ||
return this._on(exports.ServerHooks.ImpersonationError, callback); | ||
}; | ||
AccountsServer.prototype.loginWithService = function (serviceName, params, infos) { | ||
AccountsServer.prototype.loginWithPassword = function (user, password, ip, userAgent) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user; | ||
var foundUser, loginResult, error_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!this.services[serviceName]) { | ||
throw new Error("No service with the name " + serviceName + " was registered."); | ||
_a.trys.push([0, 6, , 7]); | ||
if (!user || !password) { | ||
throw new common_1.AccountsError('Unrecognized options for login request', user, 400); | ||
} | ||
return [4, this.services[serviceName].authenticate(params)]; | ||
if ((!isString(user) && !isPlainObject(user)) || !isString(password)) { | ||
throw new common_1.AccountsError('Match failed', user, 400); | ||
} | ||
foundUser = void 0; | ||
if (!this._options.passwordAuthenticator) return [3, 2]; | ||
return [4, this._externalPasswordAuthenticator(this._options.passwordAuthenticator, user, password)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new Error("Service " + serviceName + " was not able to authenticate user"); | ||
foundUser = _a.sent(); | ||
return [3, 4]; | ||
case 2: return [4, this._defaultPasswordAuthenticator(user, password)]; | ||
case 3: | ||
foundUser = _a.sent(); | ||
_a.label = 4; | ||
case 4: | ||
if (!foundUser) { | ||
throw new common_1.AccountsError('User not found', user, 403); | ||
} | ||
return [2, this.loginWithUser(user, infos)]; | ||
return [4, this.loginWithUser(foundUser, ip, userAgent)]; | ||
case 5: | ||
loginResult = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LoginSuccess, loginResult); | ||
return [2, loginResult]; | ||
case 6: | ||
error_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LoginError, error_1); | ||
throw error_1; | ||
case 7: return [2]; | ||
} | ||
@@ -164,10 +177,15 @@ }); | ||
}; | ||
AccountsServer.prototype.loginWithUser = function (user, infos) { | ||
AccountsServer.prototype._externalPasswordAuthenticator = function (authFn, user, password) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var ip, userAgent, sessionId, _a, accessToken, refreshToken, loginResult; | ||
return __generator(this, function (_a) { | ||
return [2, authFn(user, password)]; | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.loginWithUser = function (user, ip, userAgent) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var sessionId, _a, accessToken, refreshToken, loginResult; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
ip = infos.ip, userAgent = infos.userAgent; | ||
return [4, this.db.createSession(user.id, ip, userAgent)]; | ||
case 0: return [4, this.db.createSession(user.id, ip, userAgent)]; | ||
case 1: | ||
@@ -178,3 +196,3 @@ sessionId = _b.sent(); | ||
sessionId: sessionId, | ||
user: this.sanitizeUser(user), | ||
user: this._sanitizeUser(user), | ||
tokens: { | ||
@@ -190,2 +208,71 @@ refreshToken: refreshToken, | ||
}; | ||
AccountsServer.prototype.createUser = function (user) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _a, _b, password, validateNewUser, proposedUserObject, userId, error_2; | ||
return __generator(this, function (_c) { | ||
switch (_c.label) { | ||
case 0: | ||
_c.trys.push([0, 10, , 11]); | ||
if (!common_1.validators.validateUsername(user.username) && | ||
!common_1.validators.validateEmail(user.email)) { | ||
throw new common_1.AccountsError('Username or Email is required', { | ||
username: user && user.username, | ||
email: user && user.email, | ||
}); | ||
} | ||
_a = user.username; | ||
if (!_a) return [3, 2]; | ||
return [4, this.db.findUserByUsername(user.username)]; | ||
case 1: | ||
_a = (_c.sent()); | ||
_c.label = 2; | ||
case 2: | ||
if (_a) { | ||
throw new common_1.AccountsError('Username already exists', { | ||
username: user.username, | ||
}); | ||
} | ||
_b = user.email; | ||
if (!_b) return [3, 4]; | ||
return [4, this.db.findUserByEmail(user.email)]; | ||
case 3: | ||
_b = (_c.sent()); | ||
_c.label = 4; | ||
case 4: | ||
if (_b) { | ||
throw new common_1.AccountsError('Email already exists', { email: user.email }); | ||
} | ||
password = void 0; | ||
if (!user.password) return [3, 6]; | ||
return [4, this._hashAndBcryptPassword(user.password)]; | ||
case 5: | ||
password = _c.sent(); | ||
_c.label = 6; | ||
case 6: | ||
validateNewUser = this.options().validateNewUser; | ||
proposedUserObject = { | ||
username: user.username, | ||
email: user.email && user.email.toLowerCase(), | ||
password: password, | ||
profile: user.profile, | ||
}; | ||
if (!isFunction(validateNewUser)) return [3, 8]; | ||
return [4, validateNewUser(proposedUserObject)]; | ||
case 7: | ||
_c.sent(); | ||
_c.label = 8; | ||
case 8: return [4, this.db.createUser(proposedUserObject)]; | ||
case 9: | ||
userId = _c.sent(); | ||
this.hooks.emit(exports.ServerHooks.CreateUserSuccess, userId, proposedUserObject); | ||
return [2, userId]; | ||
case 10: | ||
error_2 = _c.sent(); | ||
this.hooks.emit(exports.ServerHooks.CreateUserError, error_2); | ||
throw error_2; | ||
case 11: return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.impersonate = function (accessToken, username, ip, userAgent) { | ||
@@ -202,3 +289,3 @@ return __awaiter(this, void 0, void 0, function () { | ||
try { | ||
jwt.verify(accessToken, this.options.tokenSecret); | ||
jwt.verify(accessToken, this._options.tokenSecret); | ||
} | ||
@@ -226,6 +313,6 @@ catch (err) { | ||
} | ||
if (!this.options.impersonationAuthorize) { | ||
if (!this._options.impersonationAuthorize) { | ||
return [2, { authorized: false }]; | ||
} | ||
return [4, this.options.impersonationAuthorize(user, impersonatedUser)]; | ||
return [4, this._options.impersonationAuthorize(user, impersonatedUser)]; | ||
case 4: | ||
@@ -243,3 +330,3 @@ isAuthorized = _a.sent(); | ||
tokens: impersonationTokens, | ||
user: this.sanitizeUser(impersonatedUser), | ||
user: this._sanitizeUser(impersonatedUser), | ||
}; | ||
@@ -269,4 +356,4 @@ this.hooks.emit(exports.ServerHooks.ImpersonationSuccess, user, impersonationResult); | ||
try { | ||
jwt.verify(refreshToken, this.options.tokenSecret); | ||
decodedAccessToken = jwt.verify(accessToken, this.options.tokenSecret, { | ||
jwt.verify(refreshToken, this._options.tokenSecret); | ||
decodedAccessToken = jwt.verify(accessToken, this._options.tokenSecret, { | ||
ignoreExpiration: true, | ||
@@ -298,3 +385,3 @@ }); | ||
sessionId: sessionId, | ||
user: this.sanitizeUser(user), | ||
user: this._sanitizeUser(user), | ||
tokens: tokens, | ||
@@ -319,3 +406,3 @@ }; | ||
if (isImpersonated === void 0) { isImpersonated = false; } | ||
var _a = this.options, _b = _a.tokenSecret, tokenSecret = _b === void 0 ? config_1.default.tokenSecret : _b, _c = _a.tokenConfigs, tokenConfigs = _c === void 0 ? config_1.default.tokenConfigs : _c; | ||
var _a = this._options, _b = _a.tokenSecret, tokenSecret = _b === void 0 ? config_1.default.tokenSecret : _b, _c = _a.tokenConfigs, tokenConfigs = _c === void 0 ? config_1.default.tokenConfigs : _c; | ||
var accessToken = tokens_1.generateAccessToken({ | ||
@@ -337,3 +424,3 @@ data: { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, error_1; | ||
var session, user, error_3; | ||
return __generator(this, function (_a) { | ||
@@ -356,3 +443,3 @@ switch (_a.label) { | ||
_a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutSuccess, this.sanitizeUser(user), session, accessToken); | ||
this.hooks.emit(exports.ServerHooks.LogoutSuccess, this._sanitizeUser(user), session, accessToken); | ||
return [3, 5]; | ||
@@ -364,5 +451,5 @@ case 4: throw new common_1.AccountsError('Session is no longer valid', { | ||
case 6: | ||
error_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutError, error_1); | ||
throw error_1; | ||
error_3 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutError, error_3); | ||
throw error_3; | ||
case 7: return [2]; | ||
@@ -390,7 +477,7 @@ } | ||
} | ||
if (!this.options.resumeSessionValidator) return [3, 6]; | ||
if (!this._options.resumeSessionValidator) return [3, 6]; | ||
_a.label = 3; | ||
case 3: | ||
_a.trys.push([3, 5, , 6]); | ||
return [4, this.options.resumeSessionValidator(user, session)]; | ||
return [4, this._options.resumeSessionValidator(user, session)]; | ||
case 4: | ||
@@ -404,3 +491,3 @@ _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionSuccess, user, accessToken); | ||
return [2, this.sanitizeUser(user)]; | ||
return [2, this._sanitizeUser(user)]; | ||
case 7: | ||
@@ -428,3 +515,3 @@ this.hooks.emit(exports.ServerHooks.ResumeSessionError, new common_1.AccountsError('Invalid Session', { id: session.userId })); | ||
try { | ||
decodedAccessToken = jwt.verify(accessToken, this.options.tokenSecret); | ||
decodedAccessToken = jwt.verify(accessToken, this._options.tokenSecret); | ||
sessionId = decodedAccessToken.data.sessionId; | ||
@@ -446,5 +533,91 @@ } | ||
}; | ||
AccountsServer.prototype.findUserByEmail = function (email) { | ||
return this.db.findUserByEmail(email); | ||
}; | ||
AccountsServer.prototype.findUserByUsername = function (username) { | ||
return this.db.findUserByUsername(username); | ||
}; | ||
AccountsServer.prototype.findUserById = function (userId) { | ||
return this.db.findUserById(userId); | ||
}; | ||
AccountsServer.prototype.addEmail = function (userId, newEmail, verified) { | ||
return this.db.addEmail(userId, newEmail, verified); | ||
}; | ||
AccountsServer.prototype.removeEmail = function (userId, email) { | ||
return this.db.removeEmail(userId, email); | ||
}; | ||
AccountsServer.prototype.verifyEmail = function (token) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user, verificationTokens, tokenRecord, emailRecord; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserByEmailVerificationToken(token)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('Verify email link expired'); | ||
} | ||
verificationTokens = get(user, ['services', 'email', 'verificationTokens'], []); | ||
tokenRecord = find(verificationTokens, function (t) { return t.token === token; }); | ||
if (!tokenRecord) { | ||
throw new common_1.AccountsError('Verify email link expired'); | ||
} | ||
emailRecord = find(user.emails, function (e) { return e.address === tokenRecord.address; }); | ||
if (!emailRecord) { | ||
throw new common_1.AccountsError('Verify email link is for unknown address'); | ||
} | ||
return [4, this.db.verifyEmail(user.id, emailRecord.address)]; | ||
case 2: | ||
_a.sent(); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.resetPassword = function (token, newPassword) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user, resetTokens, asArray, resetTokenRecord, emails, password; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserByResetPasswordToken(token)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('Reset password link expired'); | ||
} | ||
resetTokens = get(user, ['services', 'password', 'reset']) || []; | ||
asArray = isArray(resetTokens) ? resetTokens : [resetTokens]; | ||
resetTokenRecord = find(asArray, function (t) { return t.token === token; }); | ||
if (this._isTokenExpired(token, resetTokenRecord)) { | ||
throw new common_1.AccountsError('Reset password link expired'); | ||
} | ||
emails = user.emails || []; | ||
if (!includes(emails.map(function (email) { return email.address; }), resetTokenRecord.address)) { | ||
throw new common_1.AccountsError('Token has invalid email address'); | ||
} | ||
return [4, this._hashAndBcryptPassword(newPassword)]; | ||
case 2: | ||
password = _a.sent(); | ||
return [4, this.db.setResetPasssword(user.id, resetTokenRecord.address, password, token)]; | ||
case 3: | ||
_a.sent(); | ||
this.db.invalidateAllSessions(user.id); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.setPassword = function (userId, newPassword) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var password; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, encryption_1.bcryptPassword(newPassword)]; | ||
case 1: | ||
password = _a.sent(); | ||
return [2, this.db.setPasssword(userId, password)]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.setProfile = function (userId, profile) { | ||
@@ -485,3 +658,85 @@ return __awaiter(this, void 0, void 0, function () { | ||
}; | ||
AccountsServer.prototype.on = function (eventName, callback) { | ||
AccountsServer.prototype.sendVerificationEmail = function (address) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user, email, emails, token, resetPasswordMail; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserByEmail(address)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { email: address }); | ||
} | ||
if (!address) { | ||
email = find(user.emails, function (e) { return !e.verified; }); | ||
address = email && email.address; | ||
} | ||
emails = user.emails || []; | ||
if (!address || !includes(emails.map(function (email) { return email.address; }), address)) { | ||
throw new common_1.AccountsError('No such email address for user'); | ||
} | ||
token = tokens_1.generateRandomToken(); | ||
return [4, this.db.addEmailVerificationToken(user.id, address, token)]; | ||
case 2: | ||
_a.sent(); | ||
resetPasswordMail = this._prepareMail(address, token, this._sanitizeUser(user), 'verify-email', this.emailTemplates.verifyEmail, this.emailTemplates.from); | ||
return [4, this.email.sendMail(resetPasswordMail)]; | ||
case 3: | ||
_a.sent(); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.sendResetPasswordEmail = function (address) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user, token, resetPasswordMail; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserByEmail(address)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { email: address }); | ||
} | ||
address = this._getFirstUserEmail(user, address); | ||
token = tokens_1.generateRandomToken(); | ||
return [4, this.db.addResetPasswordToken(user.id, address, token)]; | ||
case 2: | ||
_a.sent(); | ||
resetPasswordMail = this._prepareMail(address, token, this._sanitizeUser(user), 'reset-password', this.emailTemplates.resetPassword, this.emailTemplates.from); | ||
return [4, this.email.sendMail(resetPasswordMail)]; | ||
case 3: | ||
_a.sent(); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.sendEnrollmentEmail = function (address) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user, token, enrollmentMail; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserByEmail(address)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { email: address }); | ||
} | ||
address = this._getFirstUserEmail(user, address); | ||
token = tokens_1.generateRandomToken(); | ||
return [4, this.db.addResetPasswordToken(user.id, address, token, 'enroll')]; | ||
case 2: | ||
_a.sent(); | ||
enrollmentMail = this._prepareMail(address, token, this._sanitizeUser(user), 'enroll-account', this.emailTemplates.enrollAccount, this.emailTemplates.from); | ||
return [4, this.email.sendMail(enrollmentMail)]; | ||
case 3: | ||
_a.sent(); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype._on = function (eventName, callback) { | ||
var _this = this; | ||
@@ -491,21 +746,21 @@ this.hooks.on(eventName, callback); | ||
}; | ||
AccountsServer.prototype.isTokenExpired = function (token, tokenRecord) { | ||
AccountsServer.prototype._isTokenExpired = function (token, tokenRecord) { | ||
return (!tokenRecord || | ||
Number(tokenRecord.when) + this.options.emailTokensExpiry < Date.now()); | ||
Number(tokenRecord.when) + this._options.emailTokensExpiry < Date.now()); | ||
}; | ||
AccountsServer.prototype.prepareMail = function (to, token, user, pathFragment, emailTemplate, from) { | ||
if (this.options.prepareMail) { | ||
return this.options.prepareMail(to, token, user, pathFragment, emailTemplate, from); | ||
} | ||
return this.defaultPrepareEmail(to, token, user, pathFragment, emailTemplate, from); | ||
AccountsServer.prototype._internalUserSanitizer = function (user) { | ||
return omit(user, ['services']); | ||
}; | ||
AccountsServer.prototype.sanitizeUser = function (user) { | ||
var userObjectSanitizer = this.options.userObjectSanitizer; | ||
return userObjectSanitizer(this.internalUserSanitizer(user), omit, pick); | ||
AccountsServer.prototype._sanitizeUser = function (user) { | ||
var userObjectSanitizer = this.options().userObjectSanitizer; | ||
return userObjectSanitizer(this._internalUserSanitizer(user), omit, pick); | ||
}; | ||
AccountsServer.prototype.internalUserSanitizer = function (user) { | ||
return omit(user, ['services']); | ||
AccountsServer.prototype._prepareMail = function (to, token, user, pathFragment, emailTemplate, from) { | ||
if (this._options.prepareMail) { | ||
return this._options.prepareMail(to, token, user, pathFragment, emailTemplate, from); | ||
} | ||
return this._defaultPrepareEmail(to, token, user, pathFragment, emailTemplate, from); | ||
}; | ||
AccountsServer.prototype.defaultPrepareEmail = function (to, token, user, pathFragment, emailTemplate, from) { | ||
var tokenizedUrl = this.defaultCreateTokenizedUrl(pathFragment, token); | ||
AccountsServer.prototype._defaultPrepareEmail = function (to, token, user, pathFragment, emailTemplate, from) { | ||
var tokenizedUrl = this._defaultCreateTokenizedUrl(pathFragment, token); | ||
return { | ||
@@ -518,10 +773,94 @@ from: emailTemplate.from || from, | ||
}; | ||
AccountsServer.prototype.defaultCreateTokenizedUrl = function (pathFragment, token) { | ||
var siteUrl = this.options.siteUrl || config_1.default.siteUrl; | ||
AccountsServer.prototype._defaultCreateTokenizedUrl = function (pathFragment, token) { | ||
var siteUrl = this._options.siteUrl || config_1.default.siteUrl; | ||
return siteUrl + "/" + pathFragment + "/" + token; | ||
}; | ||
AccountsServer.prototype._getFirstUserEmail = function (user, address) { | ||
if (!address && user.emails && user.emails[0]) { | ||
address = user.emails[0].address; | ||
} | ||
var emails = user.emails || []; | ||
if (!address || | ||
!includes(emails.map(function (email) { return email.address; }), address)) { | ||
throw new common_1.AccountsError('No such email address for user'); | ||
} | ||
return address; | ||
}; | ||
AccountsServer.prototype._hashAndBcryptPassword = function (password) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var hashAlgorithm, hashedPassword; | ||
return __generator(this, function (_a) { | ||
hashAlgorithm = this._options.passwordHashAlgorithm; | ||
hashedPassword = hashAlgorithm | ||
? encryption_1.hashPassword(password, hashAlgorithm) | ||
: password; | ||
return [2, encryption_1.bcryptPassword(hashedPassword)]; | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype._validateLoginWithField = function (fieldName, user) { | ||
var allowedFields = this._options.allowedLoginFields || []; | ||
var isAllowed = allowedFields.includes(fieldName); | ||
if (!isAllowed) { | ||
throw new common_1.AccountsError("Login with " + fieldName + " is not allowed!", user); | ||
} | ||
}; | ||
AccountsServer.prototype._defaultPasswordAuthenticator = function (user, password) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _a, username, email, id, foundUser, hash, hashAlgorithm, pass, isPasswordValid; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
_a = isString(user) | ||
? common_1.toUsernameAndEmail({ user: user }) | ||
: common_1.toUsernameAndEmail(__assign({}, user)), username = _a.username, email = _a.email, id = _a.id; | ||
if (!id) return [3, 2]; | ||
this._validateLoginWithField('id', user); | ||
return [4, this.db.findUserById(id)]; | ||
case 1: | ||
foundUser = _b.sent(); | ||
return [3, 6]; | ||
case 2: | ||
if (!username) return [3, 4]; | ||
this._validateLoginWithField('username', user); | ||
return [4, this.db.findUserByUsername(username)]; | ||
case 3: | ||
foundUser = _b.sent(); | ||
return [3, 6]; | ||
case 4: | ||
if (!email) return [3, 6]; | ||
this._validateLoginWithField('email', user); | ||
return [4, this.db.findUserByEmail(email)]; | ||
case 5: | ||
foundUser = _b.sent(); | ||
_b.label = 6; | ||
case 6: | ||
if (!foundUser) { | ||
throw new common_1.AccountsError('User not found', user, 403); | ||
} | ||
return [4, this.db.findPasswordHash(foundUser.id)]; | ||
case 7: | ||
hash = _b.sent(); | ||
if (!hash) { | ||
throw new common_1.AccountsError('User has no password set', user, 403); | ||
} | ||
hashAlgorithm = this._options.passwordHashAlgorithm; | ||
pass = hashAlgorithm | ||
? encryption_1.hashPassword(password, hashAlgorithm) | ||
: password; | ||
return [4, encryption_1.verifyPassword(pass, hash)]; | ||
case 8: | ||
isPasswordValid = _b.sent(); | ||
if (!isPasswordValid) { | ||
throw new common_1.AccountsError('Incorrect password', user, 403); | ||
} | ||
return [2, foundUser]; | ||
} | ||
}); | ||
}); | ||
}; | ||
return AccountsServer; | ||
}()); | ||
exports.AccountsServer = AccountsServer; | ||
exports.default = AccountsServer; | ||
exports.default = new AccountsServer(); | ||
//# sourceMappingURL=accounts-server.js.map |
@@ -1,3 +0,4 @@ | ||
import { AccountsCommonConfiguration, SessionType, UserObjectType, CreateUserType, PasswordSignupFields } from '@accounts/common'; | ||
import { EmailTemplateType } from './email'; | ||
import { AccountsCommonConfiguration, PasswordLoginUserType, SessionType, UserObjectType, PasswordType, CreateUserType, PasswordSignupFields } from '@accounts/common'; | ||
import { EmailTemplateType } from './email-templates'; | ||
export declare type PasswordAuthenticator = (user: PasswordLoginUserType, password: PasswordType) => Promise<any>; | ||
export declare type ResumeSessionValidator = (user: UserObjectType, session: SessionType) => Promise<any>; | ||
@@ -22,2 +23,3 @@ export declare type TokenExpiration = string; | ||
tokenConfigs?: TokenConfig; | ||
passwordAuthenticator?: PasswordAuthenticator; | ||
resumeSessionValidator?: ResumeSessionValidator; | ||
@@ -24,0 +26,0 @@ prepareMail?: PrepareMailFunction; |
@@ -9,2 +9,7 @@ import { UserObjectType, CreateUserType, SessionType } from '@accounts/common'; | ||
setProfile(userId: string, profile: object): Promise<object>; | ||
findPasswordHash(userId: string): Promise<string>; | ||
findUserByResetPasswordToken(token: string): Promise<UserObjectType>; | ||
setPasssword(userId: string, newPassword: string): Promise<void>; | ||
addResetPasswordToken(userId: string, email: string, token: string, reason?: string): Promise<void>; | ||
setResetPasssword(userId: string, email: string, newPassword: string, token: string): Promise<void>; | ||
findUserByEmailVerificationToken(token: string): Promise<UserObjectType>; | ||
@@ -11,0 +16,0 @@ addEmail(userId: string, newEmail: string, verified: boolean): Promise<void>; |
@@ -1,28 +0,1 @@ | ||
import { UserObjectType } from '@accounts/common'; | ||
export interface EmailTemplateType { | ||
from?: string; | ||
subject: (user?: UserObjectType) => string; | ||
text: (user: UserObjectType, url: string) => string; | ||
} | ||
export interface EmailTemplatesType { | ||
from: string; | ||
verifyEmail: EmailTemplateType; | ||
resetPassword: EmailTemplateType; | ||
enrollAccount: EmailTemplateType; | ||
} | ||
export declare const emailTemplates: { | ||
from: string; | ||
verifyEmail: { | ||
subject: () => string; | ||
text: (user: UserObjectType, url: string) => string; | ||
}; | ||
resetPassword: { | ||
subject: () => string; | ||
text: (user: UserObjectType, url: string) => string; | ||
}; | ||
enrollAccount: { | ||
subject: () => string; | ||
text: (user: UserObjectType, url: string) => string; | ||
}; | ||
}; | ||
export interface EmailConnector { | ||
@@ -29,0 +2,0 @@ sendMail(mail: object): Promise<object>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var email = require("emailjs"); | ||
exports.emailTemplates = { | ||
from: 'js-accounts <no-reply@js-accounts.com>', | ||
verifyEmail: { | ||
subject: function () { return 'Verify your account email'; }, | ||
text: function (user, url) { | ||
return "To verify your account email please click on this link: " + url; | ||
}, | ||
}, | ||
resetPassword: { | ||
subject: function () { return 'Reset your password'; }, | ||
text: function (user, url) { | ||
return "To reset your password please click on this link: " + url; | ||
}, | ||
}, | ||
enrollAccount: { | ||
subject: function () { return 'Set your password'; }, | ||
text: function (user, url) { | ||
return "To set your password please click on this link: " + url; | ||
}, | ||
}, | ||
}; | ||
var emailjs_1 = require("emailjs"); | ||
var Email = (function () { | ||
function Email(emailConfig) { | ||
if (emailConfig) { | ||
this.server = email.server.connect(emailConfig); | ||
this.server = emailjs_1.default.server.connect(emailConfig); | ||
} | ||
@@ -36,3 +15,2 @@ } | ||
console.warn('No configuration for email, you must set an email configuration'); | ||
console.log(mail); | ||
resolve(); | ||
@@ -39,0 +17,0 @@ return; |
@@ -1,6 +0,6 @@ | ||
import { AccountsServer } from './accounts-server'; | ||
import Accounts, { AccountsServer } from './accounts-server'; | ||
import * as encryption from './encryption'; | ||
import { DBInterface } from './db-interface'; | ||
import config from './config'; | ||
import { generateRandomToken } from './tokens'; | ||
import { DBInterface } from './types'; | ||
export { AccountsServer, encryption, config, DBInterface, generateRandomToken }; | ||
export default Accounts; | ||
export { AccountsServer, encryption, config, DBInterface }; |
@@ -9,4 +9,3 @@ "use strict"; | ||
exports.config = config_1.default; | ||
var tokens_1 = require("./tokens"); | ||
exports.generateRandomToken = tokens_1.generateRandomToken; | ||
exports.default = accounts_server_1.default; | ||
//# sourceMappingURL=index.js.map |
@@ -6,3 +6,3 @@ export declare const generateRandomToken: (length?: number) => string; | ||
config: object; | ||
}) => string; | ||
}) => any; | ||
export declare const generateRefreshToken: ({secret, data, config}: { | ||
@@ -12,2 +12,2 @@ secret: string; | ||
config: object; | ||
}) => string; | ||
}) => any; |
{ | ||
"name": "@accounts/server", | ||
"version": "0.1.0-alpha.5b339220", | ||
"version": "0.1.0-alpha.6732aa8e", | ||
"description": "Fullstack authentication and accounts-management", | ||
@@ -11,4 +11,4 @@ "main": "lib/index.js", | ||
"scripts": { | ||
"start": "webpack -p --config --progress --watch", | ||
"clean": "rimraf lib", | ||
"start": "tsc --watch", | ||
"precompile": "npm run clean", | ||
@@ -51,3 +51,2 @@ "compile": "tsc", | ||
"dependencies": { | ||
"@accounts/common": "^0.1.0-alpha.5b339220", | ||
"babel-polyfill": "^6.23.0", | ||
@@ -61,7 +60,12 @@ "bcryptjs": "^2.4.0", | ||
}, | ||
"peerDependencies": { | ||
"@accounts/common": "^0.1.0-alpha.6732aa8e" | ||
}, | ||
"devDependencies": { | ||
"@types/jsonwebtoken": "^7.2.3", | ||
"@types/jwt-decode": "^2.2.1", | ||
"coveralls": "^2.11.14" | ||
"@accounts/common": "^0.1.0-alpha.6732aa8e", | ||
"coveralls": "^2.11.14", | ||
"jest": "^18.0.0", | ||
"localstorage-polyfill": "^1.0.1", | ||
"rimraf": "^2.6.1" | ||
} | ||
} |
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
Sorry, the diff of this file is not supported yet
87651
1238
5
25