@accounts/server
Advanced tools
Comparing version 0.1.0-alpha.58382c10 to 0.1.0-alpha.6b56dffc
@@ -1,83 +0,26 @@ | ||
/// <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'; | ||
export interface TokenRecord { | ||
token: string; | ||
address: string; | ||
when: number; | ||
reason: string; | ||
import { UserObjectType, LoginReturnType } from '@accounts/common'; | ||
import { ConnectionInformationsType, DBInterface } from './types'; | ||
export interface AccountsServerOptions { | ||
db: DBInterface; | ||
tokenSecret: string; | ||
tokenConfigs?: { | ||
accessToken?: { | ||
expiresIn?: string; | ||
}; | ||
refreshToken?: { | ||
expiresIn?: string; | ||
}; | ||
}; | ||
} | ||
export declare type RemoveListnerHandle = () => EventEmitter; | ||
export declare const ServerHooks: { | ||
LoginSuccess: string; | ||
LoginError: string; | ||
LogoutSuccess: string; | ||
LogoutError: string; | ||
CreateUserSuccess: string; | ||
CreateUserError: string; | ||
ResumeSessionSuccess: string; | ||
ResumeSessionError: string; | ||
RefreshTokensSuccess: string; | ||
RefreshTokensError: string; | ||
ImpersonationSuccess: string; | ||
ImpersonationError: string; | ||
}; | ||
export declare class AccountsServer { | ||
private _options; | ||
export default class AccountsServer { | ||
private services; | ||
private db; | ||
private email; | ||
private emailTemplates; | ||
private hooks; | ||
config(options: AccountsServerConfiguration, db: DBInterface): void; | ||
options(): AccountsServerConfiguration; | ||
onLoginSuccess(callback: HookListener): RemoveListnerHandle; | ||
onLoginError(callback: HookListener): RemoveListnerHandle; | ||
onLogoutSuccess(callback: HookListener): RemoveListnerHandle; | ||
onLogoutError(callback: HookListener): RemoveListnerHandle; | ||
onCreateUserSuccess(callback: HookListener): RemoveListnerHandle; | ||
onCreateUserError(callback: HookListener): RemoveListnerHandle; | ||
onResumeSessionSuccess(callback: HookListener): RemoveListnerHandle; | ||
onResumeSessionError(callback: HookListener): RemoveListnerHandle; | ||
onRefreshTokensSuccess(callback: HookListener): RemoveListnerHandle; | ||
onRefreshTokensError(callback: HookListener): RemoveListnerHandle; | ||
onImpersonationSuccess(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>; | ||
impersonate(accessToken: string, username: string, ip: string, userAgent: string): Promise<ImpersonateReturnType>; | ||
refreshTokens(accessToken: string, refreshToken: string, ip: string, userAgent: string): Promise<LoginReturnType>; | ||
createTokens(sessionId: string, isImpersonated?: boolean): TokensType; | ||
logout(accessToken: string): Promise<void>; | ||
resumeSession(accessToken: string): Promise<UserObjectType>; | ||
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); | ||
private options; | ||
constructor(services: any, options: AccountsServerOptions); | ||
loginWithService(serviceName: string, params: any, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
loginWithUser(user: UserObjectType, infos: ConnectionInformationsType): Promise<LoginReturnType>; | ||
resumeSession(accessToken: string): Promise<UserObjectType | null>; | ||
private findSessionByAccessToken(accessToken); | ||
private createTokens(sessionId, isImpersonated?); | ||
private sanitizeUser(user); | ||
} | ||
declare const _default: AccountsServer; | ||
export default _default; |
@@ -46,125 +46,41 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var pick = require("lodash/pick"); | ||
var omit = require("lodash/omit"); | ||
var isString = require("lodash/isString"); | ||
var isPlainObject = require("lodash/isPlainObject"); | ||
var isFunction = require("lodash/isFunction"); | ||
var find = require("lodash/find"); | ||
var includes = require("lodash/includes"); | ||
var get = require("lodash/get"); | ||
var events_1 = require("events"); | ||
var lodash_1 = require("lodash"); | ||
var jwt = require("jsonwebtoken"); | ||
var common_1 = require("@accounts/common"); | ||
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"); | ||
exports.ServerHooks = { | ||
LoginSuccess: 'LoginSuccess', | ||
LoginError: 'LoginError', | ||
LogoutSuccess: 'LogoutSuccess', | ||
LogoutError: 'LogoutError', | ||
CreateUserSuccess: 'CreateUserSuccess', | ||
CreateUserError: 'CreateUserError', | ||
ResumeSessionSuccess: 'ResumeSessionSuccess', | ||
ResumeSessionError: 'ResumeSessionError', | ||
RefreshTokensSuccess: 'RefreshTokensSuccess', | ||
RefreshTokensError: 'RefreshTokensError', | ||
ImpersonationSuccess: 'ImpersonationSuccess', | ||
ImpersonationError: 'ImpersonationError', | ||
var defaultOptions = { | ||
tokenSecret: 'secret', | ||
tokenConfigs: { | ||
accessToken: { | ||
expiresIn: '90m', | ||
}, | ||
refreshToken: { | ||
expiresIn: '7d', | ||
}, | ||
}, | ||
}; | ||
var AccountsServer = (function () { | ||
function AccountsServer() { | ||
function AccountsServer(services, options) { | ||
this.services = services; | ||
this.options = __assign({}, defaultOptions, options); | ||
this.db = this.options.db; | ||
for (var service in this.services) { | ||
this.services[service].db = this.db; | ||
} | ||
} | ||
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.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(); | ||
} | ||
}; | ||
AccountsServer.prototype.options = function () { | ||
return this._options; | ||
}; | ||
AccountsServer.prototype.onLoginSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.LoginSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onLoginError = function (callback) { | ||
return this._on(exports.ServerHooks.LoginError, callback); | ||
}; | ||
AccountsServer.prototype.onLogoutSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.LogoutSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onLogoutError = function (callback) { | ||
return this._on(exports.ServerHooks.LogoutError, callback); | ||
}; | ||
AccountsServer.prototype.onCreateUserSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.CreateUserSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onCreateUserError = function (callback) { | ||
return this._on(exports.ServerHooks.CreateUserError, callback); | ||
}; | ||
AccountsServer.prototype.onResumeSessionSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.ResumeSessionSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onResumeSessionError = function (callback) { | ||
return this._on(exports.ServerHooks.ResumeSessionError, callback); | ||
}; | ||
AccountsServer.prototype.onRefreshTokensSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.RefreshTokensSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onRefreshTokensError = function (callback) { | ||
return this._on(exports.ServerHooks.RefreshTokensError, callback); | ||
}; | ||
AccountsServer.prototype.onImpersonationSuccess = function (callback) { | ||
return this._on(exports.ServerHooks.ImpersonationSuccess, callback); | ||
}; | ||
AccountsServer.prototype.onImpersonationError = function (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)]; | ||
} | ||
@@ -174,15 +90,10 @@ }); | ||
}; | ||
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; | ||
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; | ||
return [4, this.db.createSession(user.id, ip, userAgent)]; | ||
case 1: | ||
@@ -193,3 +104,3 @@ sessionId = _b.sent(); | ||
sessionId: sessionId, | ||
user: this._sanitizeUser(user), | ||
user: this.sanitizeUser(user), | ||
tokens: { | ||
@@ -205,93 +116,13 @@ refreshToken: refreshToken, | ||
}; | ||
AccountsServer.prototype.createUser = function (user) { | ||
AccountsServer.prototype.resumeSession = function (accessToken) { | ||
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) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, impersonatedUser, isAuthorized, newSessionId, impersonationTokens, impersonationResult, e_1; | ||
var session, user, e_1; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 6, , 7]); | ||
if (!isString(accessToken)) { | ||
throw new common_1.AccountsError('An access token is required'); | ||
} | ||
try { | ||
jwt.verify(accessToken, this._options.tokenSecret); | ||
} | ||
catch (err) { | ||
throw new common_1.AccountsError('Access token is not valid'); | ||
} | ||
_a.trys.push([0, 4, , 5]); | ||
return [4, this.findSessionByAccessToken(accessToken)]; | ||
case 1: | ||
session = _a.sent(); | ||
if (!session.valid) { | ||
throw new common_1.AccountsError('Session is not valid for user'); | ||
} | ||
if (!session.valid) return [3, 3]; | ||
return [4, this.db.findUserById(session.userId)]; | ||
@@ -301,35 +132,10 @@ case 2: | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found'); | ||
throw new Error('User not found'); | ||
} | ||
return [4, this.db.findUserByUsername(username)]; | ||
case 3: | ||
impersonatedUser = _a.sent(); | ||
if (!impersonatedUser) { | ||
throw new common_1.AccountsError("User " + username + " not found"); | ||
} | ||
if (!this._options.impersonationAuthorize) { | ||
return [2, { authorized: false }]; | ||
} | ||
return [4, this._options.impersonationAuthorize(user, impersonatedUser)]; | ||
return [2, this.sanitizeUser(user)]; | ||
case 3: return [2, null]; | ||
case 4: | ||
isAuthorized = _a.sent(); | ||
if (!isAuthorized) { | ||
return [2, { authorized: false }]; | ||
} | ||
return [4, this.db.createSession(impersonatedUser.id, ip, userAgent)]; | ||
case 5: | ||
newSessionId = _a.sent(); | ||
impersonationTokens = this.createTokens(newSessionId, true); | ||
impersonationResult = { | ||
authorized: true, | ||
tokens: impersonationTokens, | ||
user: this._sanitizeUser(impersonatedUser), | ||
}; | ||
this.hooks.emit(exports.ServerHooks.ImpersonationSuccess, user, impersonationResult); | ||
return [2, impersonationResult]; | ||
case 6: | ||
e_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ImpersonationError, e_1); | ||
throw e_1; | ||
case 7: return [2]; | ||
case 5: return [2]; | ||
} | ||
@@ -339,22 +145,17 @@ }); | ||
}; | ||
AccountsServer.prototype.refreshTokens = function (accessToken, refreshToken, ip, userAgent) { | ||
AccountsServer.prototype.findSessionByAccessToken = function (accessToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var sessionId, decodedAccessToken, session, user, tokens, result, err_1; | ||
var sessionId, decodedAccessToken, session; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 6, , 7]); | ||
if (!isString(accessToken) || !isString(refreshToken)) { | ||
throw new common_1.AccountsError('An accessToken and refreshToken are required'); | ||
if (!lodash_1.isString(accessToken)) { | ||
throw new Error('An accessToken is required'); | ||
} | ||
sessionId = void 0; | ||
try { | ||
jwt.verify(refreshToken, this._options.tokenSecret); | ||
decodedAccessToken = jwt.verify(accessToken, this._options.tokenSecret, { | ||
ignoreExpiration: true, | ||
}); | ||
decodedAccessToken = jwt.verify(accessToken, this.options.tokenSecret); | ||
sessionId = decodedAccessToken.data.sessionId; | ||
} | ||
catch (err) { | ||
throw new common_1.AccountsError('Tokens are not valid'); | ||
throw new Error('Tokens are not valid'); | ||
} | ||
@@ -365,31 +166,5 @@ return [4, this.db.findSessionById(sessionId)]; | ||
if (!session) { | ||
throw new common_1.AccountsError('Session not found'); | ||
throw new Error('Session not found'); | ||
} | ||
if (!session.valid) return [3, 4]; | ||
return [4, this.db.findUserById(session.userId)]; | ||
case 2: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { id: session.userId }); | ||
} | ||
tokens = this.createTokens(sessionId); | ||
return [4, this.db.updateSession(sessionId, ip, userAgent)]; | ||
case 3: | ||
_a.sent(); | ||
result = { | ||
sessionId: sessionId, | ||
user: this._sanitizeUser(user), | ||
tokens: tokens, | ||
}; | ||
this.hooks.emit(exports.ServerHooks.RefreshTokensSuccess, result); | ||
return [2, result]; | ||
case 4: throw new common_1.AccountsError('Session is no longer valid', { | ||
id: session.userId, | ||
}); | ||
case 5: return [3, 7]; | ||
case 6: | ||
err_1 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.RefreshTokensError, err_1); | ||
throw err_1; | ||
case 7: return [2]; | ||
return [2, session]; | ||
} | ||
@@ -401,3 +176,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, tokenSecret = _a.tokenSecret, tokenConfigs = _a.tokenConfigs; | ||
var accessToken = tokens_1.generateAccessToken({ | ||
@@ -409,443 +184,16 @@ data: { | ||
secret: tokenSecret, | ||
config: tokenConfigs.accessToken || {}, | ||
config: tokenConfigs.accessToken, | ||
}); | ||
var refreshToken = tokens_1.generateRefreshToken({ | ||
secret: tokenSecret, | ||
config: tokenConfigs.refreshToken || {}, | ||
config: tokenConfigs.refreshToken, | ||
}); | ||
return { accessToken: accessToken, refreshToken: refreshToken }; | ||
}; | ||
AccountsServer.prototype.logout = function (accessToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, error_3; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 6, , 7]); | ||
return [4, this.findSessionByAccessToken(accessToken)]; | ||
case 1: | ||
session = _a.sent(); | ||
if (!session.valid) return [3, 4]; | ||
return [4, this.db.findUserById(session.userId)]; | ||
case 2: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { id: session.userId }); | ||
} | ||
return [4, this.db.invalidateSession(session.sessionId)]; | ||
case 3: | ||
_a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutSuccess, this._sanitizeUser(user), session, accessToken); | ||
return [3, 5]; | ||
case 4: throw new common_1.AccountsError('Session is no longer valid', { | ||
id: session.userId, | ||
}); | ||
case 5: return [3, 7]; | ||
case 6: | ||
error_3 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.LogoutError, error_3); | ||
throw error_3; | ||
case 7: return [2]; | ||
} | ||
}); | ||
}); | ||
AccountsServer.prototype.sanitizeUser = function (user) { | ||
return lodash_1.omit(user, ['services']); | ||
}; | ||
AccountsServer.prototype.resumeSession = function (accessToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var session, user, e_2, e_3; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
_a.trys.push([0, 8, , 9]); | ||
return [4, this.findSessionByAccessToken(accessToken)]; | ||
case 1: | ||
session = _a.sent(); | ||
if (!session.valid) return [3, 7]; | ||
return [4, this.db.findUserById(session.userId)]; | ||
case 2: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { id: session.userId }); | ||
} | ||
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)]; | ||
case 4: | ||
_a.sent(); | ||
return [3, 6]; | ||
case 5: | ||
e_2 = _a.sent(); | ||
throw new common_1.AccountsError(e_2, { id: session.userId }, 403); | ||
case 6: | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionSuccess, user, accessToken); | ||
return [2, this._sanitizeUser(user)]; | ||
case 7: | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionError, new common_1.AccountsError('Invalid Session', { id: session.userId })); | ||
return [2, null]; | ||
case 8: | ||
e_3 = _a.sent(); | ||
this.hooks.emit(exports.ServerHooks.ResumeSessionError, e_3); | ||
throw e_3; | ||
case 9: return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.findSessionByAccessToken = function (accessToken) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var sessionId, decodedAccessToken, session; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
if (!isString(accessToken)) { | ||
throw new common_1.AccountsError('An accessToken is required'); | ||
} | ||
try { | ||
decodedAccessToken = jwt.verify(accessToken, this._options.tokenSecret); | ||
sessionId = decodedAccessToken.data.sessionId; | ||
} | ||
catch (err) { | ||
throw new common_1.AccountsError('Tokens are not valid'); | ||
} | ||
return [4, this.db.findSessionById(sessionId)]; | ||
case 1: | ||
session = _a.sent(); | ||
if (!session) { | ||
throw new common_1.AccountsError('Session not found'); | ||
} | ||
return [2, session]; | ||
} | ||
}); | ||
}); | ||
}; | ||
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, 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']); | ||
resetTokenRecord = find(resetTokens, 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) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserById(userId)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { id: userId }); | ||
} | ||
return [4, this.db.setProfile(userId, profile)]; | ||
case 2: | ||
_a.sent(); | ||
return [2]; | ||
} | ||
}); | ||
}); | ||
}; | ||
AccountsServer.prototype.updateProfile = function (userId, profile) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var user; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4, this.db.findUserById(userId)]; | ||
case 1: | ||
user = _a.sent(); | ||
if (!user) { | ||
throw new common_1.AccountsError('User not found', { id: userId }); | ||
} | ||
return [2, this.db.setProfile(userId, __assign({}, user.profile, profile))]; | ||
} | ||
}); | ||
}); | ||
}; | ||
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; | ||
this.hooks.on(eventName, callback); | ||
return function () { return _this.hooks.removeListener(eventName, callback); }; | ||
}; | ||
AccountsServer.prototype._isTokenExpired = function (token, tokenRecord) { | ||
return (!tokenRecord || | ||
Number(tokenRecord.when) + this._options.emailTokensExpiry < Date.now()); | ||
}; | ||
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._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); | ||
return { | ||
from: emailTemplate.from || from, | ||
to: to, | ||
subject: emailTemplate.subject(user), | ||
text: emailTemplate.text(user, tokenizedUrl), | ||
}; | ||
}; | ||
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,6 +0,3 @@ | ||
import Accounts, { 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 AccountsServer from './accounts-server'; | ||
export { DBInterface } from './types'; | ||
export { AccountsServer }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var accounts_server_1 = require("./accounts-server"); | ||
exports.AccountsServer = accounts_server_1.AccountsServer; | ||
var encryption = require("./encryption"); | ||
exports.encryption = encryption; | ||
var config_1 = require("./config"); | ||
exports.config = config_1.default; | ||
exports.default = accounts_server_1.default; | ||
exports.AccountsServer = accounts_server_1.default; | ||
//# sourceMappingURL=index.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var jwt = require("jsonwebtoken"); | ||
var crypto_1 = require("crypto"); | ||
var crypto = require("crypto"); | ||
exports.generateRandomToken = function (length) { | ||
if (length === void 0) { length = 43; } | ||
return crypto_1.randomBytes(length).toString('hex'); | ||
return crypto.randomBytes(length).toString('hex'); | ||
}; | ||
@@ -9,0 +9,0 @@ exports.generateAccessToken = function (_a) { |
{ | ||
"name": "@accounts/server", | ||
"version": "0.1.0-alpha.58382c10", | ||
"description": "Fullstack authentication and accounts-management", | ||
"version": "0.1.0-alpha.6b56dffc", | ||
"license": "MIT", | ||
"main": "lib/index.js", | ||
"typings": "lib/index.d.ts", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"scripts": { | ||
"start": "webpack -p --config --progress --watch", | ||
"clean": "rimraf lib", | ||
"precompile": "npm run clean", | ||
"start": "tsc --watch", | ||
"compile": "tsc", | ||
"prepublish": "npm run compile", | ||
"test": "npm run testonly", | ||
"test-ci": "npm lint && npm coverage", | ||
"testonly": "jest", | ||
"coverage": "npm run testonly -- --coverage", | ||
"coveralls": "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage" | ||
"coverage": "jest --coverage" | ||
}, | ||
"jest": { | ||
"transform": { | ||
".(ts|tsx)": "<rootDir>/../../node_modules/ts-jest/preprocessor.js" | ||
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js" | ||
}, | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx)$", | ||
"testRegex": "./__tests__/.*.(ts|tsx)$", | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js" | ||
@@ -33,34 +24,8 @@ ], | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/js-accounts/accounts/tree/master/packages/server" | ||
}, | ||
"keywords": [ | ||
"rest", | ||
"graphql", | ||
"grant", | ||
"auth", | ||
"authentication", | ||
"accounts", | ||
"users", | ||
"oauth" | ||
], | ||
"author": "Tim Mikeladze", | ||
"license": "MIT", | ||
"dependencies": { | ||
"@accounts/common": "^0.1.0-alpha.58382c10", | ||
"babel-polyfill": "^6.23.0", | ||
"bcryptjs": "^2.4.0", | ||
"crypto": "^0.0.3", | ||
"emailjs": "^1.0.8", | ||
"jsonwebtoken": "^7.2.1", | ||
"jwt-decode": "^2.1.0", | ||
"lodash": "^4.16.4" | ||
"jsonwebtoken": "^7.4.1" | ||
}, | ||
"devDependencies": { | ||
"coveralls": "^2.11.14", | ||
"jest": "^18.0.0", | ||
"localstorage-polyfill": "^1.0.1", | ||
"rimraf": "^2.6.1" | ||
"peerDependencies": { | ||
"@accounts/common": "^0.1.0" | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
109890
2
0
22
716
2
1
1
+ Addedmoment@2.30.1(transitive)
- Removedbabel-polyfill@^6.23.0
- Removedbcryptjs@^2.4.0
- Removedcrypto@^0.0.3
- Removedemailjs@^1.0.8
- Removedjwt-decode@^2.1.0
- Removedlodash@^4.16.4
- Removed@accounts/common@0.1.0-beta.14(transitive)
- Removedaddressparser@0.2.10.3.2(transitive)
- Removedbabel-polyfill@6.26.0(transitive)
- Removedbabel-runtime@6.26.0(transitive)
- Removedbcryptjs@2.4.3(transitive)
- Removedbufferjs@1.1.0(transitive)
- Removedcore-js@2.6.12(transitive)
- Removedcrypto@0.0.3(transitive)
- Removedemailjs@1.0.12(transitive)
- Removedencoding@0.1.13(transitive)
- Removediconv-lite@0.6.3(transitive)
- Removedjwt-decode@2.2.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedmimelib@0.2.14(transitive)
- Removedmoment@2.15.2(transitive)
- Removedregenerator-runtime@0.10.50.11.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedstarttls@1.0.1(transitive)
Updatedjsonwebtoken@^7.4.1