@accounts/server
Advanced tools
Comparing version 0.0.21 to 0.1.0-alpha.42868044
/// <reference types="@types/node" /> | ||
import { EventEmitter } from 'events'; | ||
import { UserObjectType, CreateUserType, PasswordLoginUserType, LoginReturnType, TokensType, SessionType, ImpersonateReturnType, PasswordType, HookListener } from '@accounts/common'; | ||
import { AccountsServerConfiguration, PasswordAuthenticator } from './config'; | ||
import { DBInterface } from './db-interface'; | ||
import { UserObjectType, LoginReturnType, TokensType, SessionType, ImpersonateReturnType, HookListener } from '@accounts/common'; | ||
import { EmailConnector, EmailTemplateType } from './email'; | ||
import { AccountsServerOptions, ConnectionInformationsType } from './types'; | ||
export interface TokenRecord { | ||
@@ -28,9 +28,10 @@ token: string; | ||
export declare class AccountsServer { | ||
private _options; | ||
options: AccountsServerOptions; | ||
email: EmailConnector; | ||
private services; | ||
private db; | ||
private email; | ||
private emailTemplates; | ||
private hooks; | ||
config(options: AccountsServerConfiguration, db: DBInterface): void; | ||
options(): AccountsServerConfiguration; | ||
constructor(options: AccountsServerOptions, services: any); | ||
getServices(): any; | ||
getOptions(): AccountsServerOptions; | ||
onLoginSuccess(callback: HookListener): RemoveListnerHandle; | ||
@@ -48,6 +49,4 @@ onLoginError(callback: HookListener): RemoveListnerHandle; | ||
onImpersonationError(callback: HookListener): RemoveListnerHandle; | ||
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>; | ||
loginWithService(serviceName: string, params: any, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
loginWithUser(user: UserObjectType, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
impersonate(accessToken: string, username: string, ip: string, userAgent: string): Promise<ImpersonateReturnType>; | ||
@@ -59,28 +58,13 @@ 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>; | ||
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); | ||
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); | ||
} | ||
declare const _default: AccountsServer; | ||
export default _default; | ||
export default AccountsServer; |
@@ -49,8 +49,2 @@ "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"); | ||
@@ -60,6 +54,17 @@ 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 email_templates_1 = require("./email-templates"); | ||
var defaultOptions = { | ||
tokenSecret: 'secret', | ||
tokenConfigs: { | ||
accessToken: { | ||
expiresIn: '90m', | ||
}, | ||
refreshToken: { | ||
expiresIn: '7d', | ||
}, | ||
}, | ||
emailTemplates: email_1.emailTemplates, | ||
userObjectSanitizer: function (user) { return user; }, | ||
}; | ||
exports.ServerHooks = { | ||
@@ -80,94 +85,76 @@ LoginSuccess: 'LoginSuccess', | ||
var AccountsServer = (function () { | ||
function AccountsServer() { | ||
} | ||
AccountsServer.prototype.config = function (options, db) { | ||
this._options = __assign({}, config_1.default, options); | ||
if (!db) { | ||
function AccountsServer(options, services) { | ||
this.options = __assign({}, defaultOptions, options); | ||
if (!this.options.db) { | ||
throw new common_1.AccountsError('A database driver is required'); | ||
} | ||
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.services = services; | ||
this.db = this.options.db; | ||
for (var service in this.services) { | ||
this.services[service].setStore(this.db); | ||
this.services[service].server = this; | ||
} | ||
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.options = function () { | ||
return this._options; | ||
AccountsServer.prototype.getOptions = 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.loginWithPassword = function (user, password, ip, userAgent) { | ||
AccountsServer.prototype.loginWithService = function (serviceName, params, infos) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var foundUser, loginResult, error_1; | ||
var user; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 6, , 7]); | ||
if (!user || !password) { | ||
throw new common_1.AccountsError('Unrecognized options for login request', user, 400); | ||
if (!this.services[serviceName]) { | ||
throw new Error("No service with the name " + serviceName + " was registered."); | ||
} | ||
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)]; | ||
return [4, this.services[serviceName].authenticate(params)]; | ||
case 1: | ||
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); | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new Error("Service " + serviceName + " was not able to authenticate user"); | ||
} | ||
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]; | ||
return [2, this.loginWithUser(user, infos)]; | ||
} | ||
@@ -177,16 +164,14 @@ }); | ||
}; | ||
AccountsServer.prototype._externalPasswordAuthenticator = function (authFn, user, password) { | ||
AccountsServer.prototype.loginWithUser = function (user, infos) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
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; | ||
var ip, userAgent, sessionId, _a, accessToken, refreshToken, loginResult, e_1; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: return [4, this.db.createSession(user.id, ip, userAgent)]; | ||
case 0: | ||
ip = infos.ip, userAgent = infos.userAgent; | ||
_b.label = 1; | ||
case 1: | ||
_b.trys.push([1, 3, , 4]); | ||
return [4, this.db.createSession(user.id, ip, userAgent)]; | ||
case 2: | ||
sessionId = _b.sent(); | ||
@@ -196,3 +181,3 @@ _a = this.createTokens(sessionId), accessToken = _a.accessToken, refreshToken = _a.refreshToken; | ||
sessionId: sessionId, | ||
user: this._sanitizeUser(user), | ||
user: this.sanitizeUser(user), | ||
tokens: { | ||
@@ -203,72 +188,9 @@ refreshToken: refreshToken, | ||
}; | ||
this.hooks.emit(exports.ServerHooks.LoginSuccess, user); | ||
return [2, loginResult]; | ||
} | ||
}); | ||
}); | ||
}; | ||
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]; | ||
e_1 = _b.sent(); | ||
this.hooks.emit(exports.ServerHooks.LoginError, e_1); | ||
throw e_1; | ||
case 4: return [2]; | ||
} | ||
@@ -280,3 +202,3 @@ }); | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, impersonatedUser, isAuthorized, newSessionId, impersonationTokens, impersonationResult, e_1; | ||
var session, user, impersonatedUser, isAuthorized, newSessionId, impersonationTokens, impersonationResult, e_2; | ||
return __generator(this, function (_a) { | ||
@@ -290,3 +212,3 @@ switch (_a.label) { | ||
try { | ||
jwt.verify(accessToken, this._options.tokenSecret); | ||
jwt.verify(accessToken, this.options.tokenSecret); | ||
} | ||
@@ -314,6 +236,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: | ||
@@ -331,3 +253,3 @@ isAuthorized = _a.sent(); | ||
tokens: impersonationTokens, | ||
user: this._sanitizeUser(impersonatedUser), | ||
user: this.sanitizeUser(impersonatedUser), | ||
}; | ||
@@ -337,5 +259,5 @@ this.hooks.emit(exports.ServerHooks.ImpersonationSuccess, user, impersonationResult); | ||
case 6: | ||
e_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ImpersonationError, e_1); | ||
throw e_1; | ||
e_2 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ImpersonationError, e_2); | ||
throw e_2; | ||
case 7: return [2]; | ||
@@ -358,4 +280,4 @@ } | ||
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, | ||
@@ -387,3 +309,3 @@ }); | ||
sessionId: sessionId, | ||
user: this._sanitizeUser(user), | ||
user: this.sanitizeUser(user), | ||
tokens: tokens, | ||
@@ -408,3 +330,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({ | ||
@@ -426,3 +348,3 @@ data: { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, error_3; | ||
var session, user, error_1; | ||
return __generator(this, function (_a) { | ||
@@ -445,3 +367,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]; | ||
@@ -453,5 +375,5 @@ case 4: throw new common_1.AccountsError('Session is no longer valid', { | ||
case 6: | ||
error_3 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutError, error_3); | ||
throw error_3; | ||
error_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutError, error_1); | ||
throw error_1; | ||
case 7: return [2]; | ||
@@ -464,3 +386,3 @@ } | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, e_2, e_3; | ||
var session, user, e_3, e_4; | ||
return __generator(this, function (_a) { | ||
@@ -480,7 +402,7 @@ switch (_a.label) { | ||
} | ||
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: | ||
@@ -490,7 +412,7 @@ _a.sent(); | ||
case 5: | ||
e_2 = _a.sent(); | ||
throw new common_1.AccountsError(e_2, { id: session.userId }, 403); | ||
e_3 = _a.sent(); | ||
throw new common_1.AccountsError(e_3, { id: session.userId }, 403); | ||
case 6: | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionSuccess, user, accessToken); | ||
return [2, this._sanitizeUser(user)]; | ||
return [2, this.sanitizeUser(user)]; | ||
case 7: | ||
@@ -500,5 +422,5 @@ this.hooks.emit(exports.ServerHooks.ResumeSessionError, new common_1.AccountsError('Invalid Session', { id: session.userId })); | ||
case 8: | ||
e_3 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionError, e_3); | ||
throw e_3; | ||
e_4 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionError, e_4); | ||
throw e_4; | ||
case 9: return [2]; | ||
@@ -519,3 +441,3 @@ } | ||
try { | ||
decodedAccessToken = jwt.verify(accessToken, this._options.tokenSecret); | ||
decodedAccessToken = jwt.verify(accessToken, this.options.tokenSecret); | ||
sessionId = decodedAccessToken.data.sessionId; | ||
@@ -537,92 +459,5 @@ } | ||
}; | ||
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, tokenAddress, 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 || []; | ||
tokenAddress = resetTokenRecord.email || resetTokenRecord.address; | ||
if (!includes(emails.map(function (email) { return email.address; }), tokenAddress)) { | ||
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) { | ||
@@ -663,85 +498,3 @@ return __awaiter(this, void 0, void 0, function () { | ||
}; | ||
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) { | ||
AccountsServer.prototype.on = function (eventName, callback) { | ||
var _this = this; | ||
@@ -751,21 +504,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._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._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._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._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 { | ||
@@ -778,94 +531,10 @@ 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 = new AccountsServer(); | ||
exports.default = AccountsServer; | ||
//# sourceMappingURL=accounts-server.js.map |
@@ -1,4 +0,3 @@ | ||
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>; | ||
import { AccountsCommonConfiguration, SessionType, UserObjectType, CreateUserType, PasswordSignupFields } from '@accounts/common'; | ||
import { EmailTemplateType } from './email'; | ||
export declare type ResumeSessionValidator = (user: UserObjectType, session: SessionType) => Promise<any>; | ||
@@ -23,3 +22,2 @@ export declare type TokenExpiration = string; | ||
tokenConfigs?: TokenConfig; | ||
passwordAuthenticator?: PasswordAuthenticator; | ||
resumeSessionValidator?: ResumeSessionValidator; | ||
@@ -26,0 +24,0 @@ prepareMail?: PrepareMailFunction; |
@@ -0,1 +1,28 @@ | ||
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 { | ||
@@ -2,0 +29,0 @@ sendMail(mail: object): Promise<object>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var emailjs_1 = require("emailjs"); | ||
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 Email = (function () { | ||
function Email(emailConfig) { | ||
if (emailConfig) { | ||
this.server = emailjs_1.default.server.connect(emailConfig); | ||
this.server = email.server.connect(emailConfig); | ||
} | ||
@@ -15,2 +36,3 @@ } | ||
console.warn('No configuration for email, you must set an email configuration'); | ||
console.log(mail); | ||
resolve(); | ||
@@ -17,0 +39,0 @@ return; |
@@ -1,6 +0,7 @@ | ||
import Accounts, { AccountsServer } from './accounts-server'; | ||
import { AccountsServer } from './accounts-server'; | ||
import * as encryption from './encryption'; | ||
import { DBInterface } from './db-interface'; | ||
import config from './config'; | ||
export default Accounts; | ||
export { AccountsServer, encryption, config, DBInterface }; | ||
import { generateRandomToken } from './tokens'; | ||
import { AuthService, DBInterface } from './types'; | ||
export default AccountsServer; | ||
export { AccountsServer, AuthService, encryption, config, DBInterface, generateRandomToken }; |
@@ -9,3 +9,5 @@ "use strict"; | ||
exports.config = config_1.default; | ||
exports.default = accounts_server_1.default; | ||
var tokens_1 = require("./tokens"); | ||
exports.generateRandomToken = tokens_1.generateRandomToken; | ||
exports.default = accounts_server_1.AccountsServer; | ||
//# sourceMappingURL=index.js.map |
@@ -6,3 +6,3 @@ export declare const generateRandomToken: (length?: number) => string; | ||
config: object; | ||
}) => any; | ||
}) => string; | ||
export declare const generateRefreshToken: ({secret, data, config}: { | ||
@@ -12,2 +12,2 @@ secret: string; | ||
config: object; | ||
}) => any; | ||
}) => string; |
{ | ||
"name": "@accounts/server", | ||
"version": "0.0.21", | ||
"version": "0.1.0-alpha.42868044", | ||
"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", | ||
@@ -20,2 +20,3 @@ "compile": "tsc", | ||
"testonly": "jest", | ||
"test:watch": "jest --watch", | ||
"coverage": "npm run testonly -- --coverage", | ||
@@ -52,3 +53,3 @@ "coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" | ||
"dependencies": { | ||
"@accounts/common": "^0.0.21", | ||
"@accounts/common": "^0.1.0-alpha.42868044", | ||
"babel-polyfill": "^6.23.0", | ||
@@ -63,7 +64,6 @@ "bcryptjs": "^2.4.0", | ||
"devDependencies": { | ||
"coveralls": "^2.11.14", | ||
"jest": "^18.0.0", | ||
"localstorage-polyfill": "^1.0.1", | ||
"rimraf": "^2.6.1" | ||
"@types/jsonwebtoken": "^7.2.3", | ||
"@types/jwt-decode": "^2.2.1", | ||
"coveralls": "^2.11.14" | ||
} | ||
} |
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
3
63189
951
+ Added@accounts/common@0.1.0-beta.14(transitive)
- Removed@accounts/common@0.0.21(transitive)